Odoo API Documentation: A Practical Developer Reference
A structured reference for the Odoo API — where the official docs live, the request envelope, all 9 actions with their argument shapes, and the full error-code table.
ODXProxy Team · Jul 13, 2026 · 8 min read

Search for Odoo API documentation and you land in a few different places: Odoo's official External API page, scattered forum threads, and the ORM reference. None of them is a single copy-pasteable reference you can keep open while you build. This page is that reference. It points you at the authoritative Odoo docs, then lays out the whole practical surface — authentication, the request envelope, every action and its argument shape, the response format, and the complete error-code table — in the order you actually need them.
Where the official documentation lives
Start by bookmarking Odoo's own docs; this page complements them, it doesn't replace them:
- External API — the canonical guide to calling Odoo over XML-RPC and JSON-RPC, including
authenticateandexecute_kw. This is the protocol reference. - ORM reference — the model layer:
search,read,search_read,create,write,unlink, domains, and field types. Everything you call through the API is defined here. fields_get— not really documentation but the best reference of all: called against a live instance, it returns the exact fields, types, and relations of any model. When the docs and a real database disagree, the database wins.
The rest of this page is the integration reference — how those primitives look when you call them through the ODXProxy JSON-RPC endpoint, which is where most of the fiddly, undocumented details bite.
Protocol: JSON-RPC 2.0, not REST
The single most important fact about the Odoo API: it is JSON-RPC 2.0, not REST. There is no
GET /partners and no route per model. You call one universal method and name the model and operation
as arguments. If you were expecting a REST route table, read
Odoo API endpoints: the one route you actually call — it explains why the
list is so short. For the XML-RPC-versus-JSON-RPC transport comparison, see
The Odoo External API: a JSON-RPC integration guide.
Authentication: two keys, never conflated
Through the proxy, every request carries two independent secrets. Mixing them up is the most common
cause of a mysterious 401:
x-api-key— the proxy's key. An HTTP header that authenticates your app to the proxy. It is the same for every Odoo instance behind that proxy.odoo_instance.api_key— the Odoo user's key. Sent per request inside the body, it authenticates the actual ERP call. Generate it in Odoo under Preferences → Account Security → New API Key.
They are never the same value. The full setup, including where each key comes from, is in How to authenticate to the Odoo API.
The endpoint
Every data call goes to one route:
POST /api/odoo/executeTwo optional operational endpoints sit alongside it and are not part of the data path:
POST /api/odoo/version— returns the target instance's version banner. Needs the proxyx-api-keyand the instance URL, but no Odoo user credentials, so it makes a clean connectivity check. See Testing your Odoo API connection.GET /_/license,GET /_/about,GET /_/metrics— license status, build info, and Prometheus metrics. Ops tooling, not something to wire into your call path.
The request envelope
Every call to /api/odoo/execute is the same JSON shape. This is the field-by-field reference:
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | yes | A correlation id you choose; echoed back in the response. |
action | string | yes | One of the 9 allowed actions (below). |
model_id | string | yes | The Odoo model, e.g. res.partner, sale.order. |
params | array | no (default []) | Positional arguments for the action. |
keyword | object | no (default {}) | Keyword arguments for the action. |
fn_name | string | only for call_method | The model method to invoke. |
odoo_instance | object | yes | Target instance credentials (below). |
The odoo_instance object identifies which ERP the proxy forwards to:
| Field | Type | Meaning |
|---|---|---|
url | string | The Odoo base URL, e.g. https://erp.example.com. |
db | string | The database name. |
user_id | integer | The numeric Odoo user id (uid) the call runs as. |
api_key | string | That user's Odoo API key. |
Remember the two shape rules that trip people up: params is always a JSON array (default []) and
keyword is always a JSON object (default {}). A common mistake is passing a bare object where an
array is expected.
You may also send an optional x-request-timeout header (seconds, default 15) to bound how long the
proxy waits on Odoo before returning a timeout.
The 9 actions
The proxy accepts exactly nine actions. Anything else is rejected with HTTP 400 / -32001 before
it reaches Odoo. This is the per-action argument reference:
| Action | params (positional) | Common keyword |
|---|---|---|
search_count | [domain] | — |
search | [domain] | offset, limit, order |
read | [ids] | fields |
fields_get | [] or [fields] | attributes |
search_read | [domain] | fields, limit, offset, order |
create | [values] | — |
write | [ids, values] | — |
unlink | [ids] | — |
call_method | method's own positional args | method's own keyword args |
A domain is a list of triples, so it nests one level deep inside params: a search_read filtered
on is_company looks like params: [[["is_company", "=", true]]]. Domain operators and relational
traversal are covered in Odoo domain filters explained.
The first eight actions are the CRUD-and-query core. The ninth, call_method, is the escape hatch:
any model method the user is allowed to call — action_post on account.move, action_confirm on
sale.order — you reach by setting action to call_method and fn_name to the method name. It is
detailed in Calling Odoo model methods with call_method.
A worked request and response
A complete search_read returning five companies. The model and action are body fields, not part of
the URL:
curl -X POST https://your-proxy.example.com/api/odoo/execute \
-H "Content-Type: application/json" \
-H "x-api-key: $ODX_PROXY_KEY" \
-d '{
"id": "docs-demo-1",
"action": "search_read",
"model_id": "res.partner",
"params": [[["is_company", "=", true]]],
"keyword": { "fields": ["name", "email"], "limit": 5 },
"odoo_instance": {
"url": "https://erp.example.com",
"db": "prod",
"user_id": 2,
"api_key": "<the Odoo user API key>"
}
}'On success you get HTTP 200 and a JSON-RPC envelope whose result holds Odoo's data:
{
"jsonrpc": "2.0",
"id": "docs-demo-1",
"result": [
{ "id": 9, "name": "Gemini Furniture", "email": "info@gemini.example" },
{ "id": 14, "name": "Azure Interior", "email": "hello@azure.example" }
]
}Error handling: two layers, and the HTTP 200 trap
The one rule that catches nearly every new integration: you cannot infer success from the HTTP status alone. There are two layers of failure, checked in order.
Proxy-level failures use a non-200 status. A wrong proxy key returns 401; an action outside the
allowlist returns 400; an upstream timeout returns 504.
Odoo's own errors come back with HTTP 200. If the proxy accepted the request but Odoo rejected it
— a bad Odoo API key, a missing access right, a validation failure — the logic error is passed straight
through with a 200 and a populated error object:
{
"jsonrpc": "2.0",
"id": "docs-demo-1",
"error": {
"code": 200,
"message": "You are not allowed to create 'Contact' records.",
"data": { "name": "odoo.exceptions.AccessError" }
}
}200 response is not proof of success. Check the HTTP status first, and then — even on a 200 — check whether the body has a populated error field before you read result.The proxy's own JSON-RPC error codes, for the non-200 layer:
| Code | Meaning |
|---|---|
-32000 | Authentication failed (bad or missing x-api-key). |
-32001 | Invalid action (not one of the nine). |
-32002 | call_method sent without a non-empty fn_name. |
-32003 | Upstream Odoo call timed out. |
-32004 | Bad gateway — network failure reaching Odoo. |
-32005 | Internal proxy error decoding Odoo's response. |
0 | License invalid. |
The full two-layer model, with a robust client that checks both, is in Odoo API error handling.
Skip the envelope: the SDKs
Everything above is the wire protocol. In application code you rarely hand-build these envelopes — the official SDKs wrap them in typed methods and turn the two-layer error model into exceptions. There are clients for Python, JavaScript/TypeScript, PHP, Java/Kotlin, Swift, and .NET. Start with the Python SDK for the least boilerplate.
Where to go next
This page is the map; the linked guides are the territory.
- Get the credentials right in How to authenticate to the Odoo API.
- Understand why there's no REST route table in Odoo API endpoints: the one route you actually call.
- Master reads and writes with Odoo search_read, in depth and create, write, unlink.
- Reach any method beyond the core eight with call_method.
- Read the complete request and response contract in the API reference.