Execution Layer for Odoo
Troubleshooting.
The common things that go wrong when connecting an AI client to the Execution Layer, and what each one means, from a 401 on the endpoint to an ambiguous target or a client in the cloud that cannot reach your Odoo.
Most issues fall into a handful of shapes. The Execution Layer’s errors are structured, so the code usually tells you the fix.
A 401 with WWW-Authenticate
The request is not authenticated. Run the OAuth flow for your client (see connecting an AI
client): the client should open the Odoo login and you
sign in as yourself, then click Allow. For scripts, send a scoped API key as
Authorization: Bearer nmk_....
TOOL_NOT_AVAILABLE or MODEL_FORBIDDEN
The tool or model is out of scope for this user. This is governance working as intended, not a bug.
The refusal carries a fix_location pointing at the bundle or connection to adjust. Widen the
bundle or the connection’s access level, then retry.
APPROVAL_REQUIRED
The action is risk-classed and was staged rather than run. A person, who can be someone other than
the requester, approves it in Odoo’s activity inbox. Once approved, the agent retries the identical
call with the approval_request_id. If the payload changed in between, the retry returns
APPROVAL_STALE; re-stage it.
AMBIGUOUS_TARGET
A human reference matched more than one record. The refusal returns the candidate list. Resolve it by a unique field, an email or a code, or ask the person which record they meant, then call again. The layer will not guess.
A cloud client cannot connect
ChatGPT and the Claude web app run in the cloud, so they need your Odoo reachable over the public
internet on HTTPS, with proxy_mode = True behind your reverse proxy. Local clients (Claude Code and
Desktop, Cursor) do not: a LAN or VPN is enough. Check the reachability
table.
IDEMPOTENCY_CONFLICT
The same idempotency key was reused with a different payload. Use a fresh key for a genuinely different write; reuse the same key only to safely retry the same one.
A read looks short
Check the read’s truncated and total_count. A bounded read reports exactly how much it left out,
so page through with next_offset before treating the result as complete. This is the
truncation contract, doing its job.
Multiple databases
If the host serves more than one database and a client lands on the wrong one, send
X-Odoo-Database: <db>. The supported deployment is one database per host, which avoids this.
Still stuck? Everything the AI did is in the audit, and every refusal is recorded there with its code and detail.