← Blog
odooapidocumentationreferencejson-rpcexternal-api

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

Odoo API Documentation: A Practical Developer Reference — ODXProxy blog cover

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 authenticate and execute_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/execute

Two 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 proxy x-api-key and 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:

FieldTypeRequiredMeaning
idstringyesA correlation id you choose; echoed back in the response.
actionstringyesOne of the 9 allowed actions (below).
model_idstringyesThe Odoo model, e.g. res.partner, sale.order.
paramsarrayno (default [])Positional arguments for the action.
keywordobjectno (default {})Keyword arguments for the action.
fn_namestringonly for call_methodThe model method to invoke.
odoo_instanceobjectyesTarget instance credentials (below).

The odoo_instance object identifies which ERP the proxy forwards to:

FieldTypeMeaning
urlstringThe Odoo base URL, e.g. https://erp.example.com.
dbstringThe database name.
user_idintegerThe numeric Odoo user id (uid) the call runs as.
api_keystringThat 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:

Actionparams (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_methodmethod's own positional argsmethod'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" }
  }
}
A 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:

CodeMeaning
-32000Authentication failed (bad or missing x-api-key).
-32001Invalid action (not one of the nine).
-32002call_method sent without a non-empty fn_name.
-32003Upstream Odoo call timed out.
-32004Bad gateway — network failure reaching Odoo.
-32005Internal proxy error decoding Odoo's response.
0License 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.