Read-only Odoo API key for ChatGPT, Claude or an MCP server (Odoo 20)
What a key can do by default, what access rights cannot express, and the write values that silently delete lines

Odoo 20 has no read-only API key. A key can do everything its user can do, through any public method. Here is what that means when you hand a key to ChatGPT, Claude, an MCP server or an n8n flow, based on tests on a local Odoo 20 Community database on 2026-10-09.
- A key has no permissions of its own. In Odoo 20 Community the only scope offered for a new key is
RPC. The scope decides where the key may sign in, not what it may do. - The JSON-2 API calls any public method.
/json/2/<model>/<method>runsunlink,action_confirmoraction_cancelas readily assearch_read, within the rights of the key's user. - The free way to limit a key is a dedicated user with minimal access rights, as Odoo's documentation recommends. Access rights work per model and per operation: they cannot say "may confirm but not cancel" or "at most 10 records per call".
- Watch the values an agent sends. On a linked list such as order lines,
false,null, a plain list of ids and the command number2.0all removed lines on our test database. - A truly read-only call is one whose database transaction is read-only: PostgreSQL then refuses every write, including writes Odoo makes with elevated rights.
What a key can do by default
A user creates a key in My Preferences > Security. The key signs in as that user, with all of that user's access rights. In Odoo 20 Community, the scope field of a new key offers a single choice, RPC. Odoo's documentation describes an MCP scope for Odoo's own MCP server, which is not part of Community; we did not test it.
The JSON-2 endpoint looks the method up by name and calls it. On 2026-10-09 one admin key read sales orders with search_read, then confirmed one with:
POST /json/2/sale.order/action_confirm
Authorization: bearer <key>
{"ids": [3]}
The answer was true and the order moved to Sales Order. Nothing in the key said it was meant for reading.
Two smaller facts about keys in Odoo 20: a user who is not an administrator must give a key an expiry date ("The API key must have an expiration date"), and every call to /xmlrpc, /xmlrpc/2 or /jsonrpc logs that these endpoints are "deprecated in Odoo 19 and scheduled for removal in Odoo 22". They still accept keys.
The free way: a dedicated user
Odoo's documentation on the JSON-2 API recommends dedicated bot users for integrations, with the minimum permissions they need. For an AI assistant that only answers questions:
- Create a user for the assistant and give it only read access to the models it needs (a group of your own with read rights works).
- Create the API key while signed in as that user.
- Give the assistant that key, never a password.
Where this stops:
- Each integration needs its own user; check what an extra user costs on your plan.
- Groups add up. If one group of the user grants delete on a model, no other group takes it away.
- A user who may write to orders may also call every button on them. Access rights have no list of allowed methods.
- Access rights have no limit on how many records one call may change.
Write values that remove order lines
AI agents write JSON. Odoo reads some values of a linked list (one2many or many2many) in ways that are easy to miss. We wrote each value below to the order_line field of a sales order on our test database (2026-10-09), with a user allowed to edit order lines. The lines of a sales order are deleted when they lose their order.
Value of order_line | How Odoo reads it | Result |
|---|---|---|
false | Remove all lines | All lines deleted |
null | Remove all lines | All lines deleted |
[8] (a plain list of ids) | Replace the lines with line 8 | The other lines deleted |
[[2.0, 10]] | Delete line 10 (2.0 is compared as the number 2) | Line 10 deleted |
[[5.0]] | Remove all lines | All lines deleted |
[["2", 12]] | Not a command | Nothing deleted |
We saw the same through XML-RPC (false and 2.0) and JSON-RPC (false). If you build a tool for an agent, refuse false, null and plain id lists on linked fields unless the agent is meant to replace them, and check command numbers with ==, as Odoo does, not by type.
Making a key read-only in the database
Checking method names ("allow search_read, refuse write") is not enough on its own: a method that looks like a read can write, and Odoo code often writes with elevated rights. What holds is a read-only transaction. After SET TRANSACTION READ ONLY, PostgreSQL refuses every INSERT, UPDATE and DELETE until the transaction ends.
On 2026-10-09 we ran calls of one key inside a read-only transaction. Ten kinds of reads worked (search_read, read, fields_get, name_search, search_count, formatted_read_group, web_search_read, web_read, context_get, and reading messages). Five kinds of writes failed, including action_confirm, which writes stock records with elevated rights:
cannot execute INSERT in a read-only transaction
The read-only mode has to be set again if Odoo retries the call after a serialization error, because a retry starts a new transaction.
Per-key rules as a module
We packaged these checks as an Odoo 20 module, AI API Key Permissions (paid, by GRAMSHIFT, which publishes this site). It adds a rule to each API key: read-only (the transaction above), no deletes (refused at Odoo's delete entry point during the key's calls), only the methods you list, and a limit on records per call. Its page lists exactly what each rule covers. A dedicated user for each tool, as above, works well together with it.
How this was tested: on a local Odoo 20 Community database (Odoo source as of 2026-10-06) with test sales orders, over JSON-2, XML-RPC and JSON-RPC, on 2026-10-09. The order-line results were read back from the database after each call. Statements about Odoo's MCP server and about dedicated bot users come from Odoo's documentation; the MCP server was not tested. This page is not affiliated with Odoo S.A. On the use of AI: the test scripts, the module and this article were written by Claude, an AI model, working for GRAMSHIFT; the results quoted here were compared against the saved outputs of those scripts.