Odoo API Endpoints: The One Route You Actually Call
Searching for a table of Odoo API endpoints? There isn't one — the External API is a single method behind a handful of routes. Here is the real map.
ODXProxy Team · Jul 8, 2026 · 8 min read

If you came here looking for a list of Odoo API endpoints — a REST table with GET /partners,
POST /orders, and one route per model — the honest answer is that it does not exist, and that is
good news. Odoo does not expose a sprawling REST surface you have to learn model by model. Its
External API is a single universal method behind a handful of endpoints, and once you know that
method you can reach every model in the database. This guide maps the endpoints that actually exist,
explains why there are so few of them, and shows the one route you will spend all your time calling.
Why there is no endpoint per model
Most APIs give you a resource tree: an endpoint for customers, another for invoices, another for
products. Odoo takes the opposite approach. It exposes one server-side method, execute_kw, that
sits in front of the entire ORM. Every operation — reading partners, posting an invoice, creating a
sale order — is the same call with a different model name and method name.
That is why "list all the Odoo API endpoints" is the wrong question. The thing that varies between operations is not the URL; it is the model and the method you pass as arguments. Change those two values and you have a different operation, over the same endpoint. There is no route to version, no resource tree to memorize, and nothing to build server-side before you can call a model.
Odoo's own endpoints: the short list
Talking to Odoo directly, the External API lives behind just a few routes:
/xmlrpc/2/common— the XML-RPC endpoint for session-level calls likeauthenticate, which exchanges your database, login, and API key for a numeric user id (uid)./xmlrpc/2/object— the XML-RPC endpoint that exposesexecute_kw, i.e. the actual data calls./jsonrpc— the JSON-RPC 2.0 endpoint. It reaches the sameexecute_kwwith a JSON payload instead of XML, and it is the cleaner choice for anything new./web/session/authenticate— the session-cookie login used by the web client; occasionally used by integrations that want a session rather than an API key.
That is essentially the whole map. Whether you go over XML-RPC or JSON-RPC, you are calling the same
execute_kw underneath — the transport is just how the request is serialized on the wire. If you want
the transport comparison in depth, see
The Odoo External API: a JSON-RPC integration guide.
The one endpoint you call through a proxy
Talking to those endpoints directly means every client holds Odoo database credentials, tracks a per-instance URL, and re-implements the same request envelope. ODXProxy is a reverse proxy that sits in front of one or more Odoo instances and collapses all of that into a single JSON-RPC 2.0 endpoint:
POST /api/odoo/executeYour application talks to that one route; the proxy forwards each call to the Odoo instance named in
the request body. There is no per-model endpoint here either — the same POST /api/odoo/execute
serves your whole ERP, and the model and action ride in the JSON body.
That design introduces two secrets you must keep straight, because conflating them is the most common
cause of a mysterious 401:
x-api-key— the proxy's key. It authenticates your app to the proxy and travels as an HTTP header. It is the same for every Odoo instance behind that proxy.odoo_instance.api_key— the Odoo user's key. It authenticates the actual ERP call and is sent per request inside the body.
They are never the same value. Set up the credentials side of this in How to authenticate to the Odoo API.
The 9 actions that replace a REST route table
Because the endpoint never changes, the thing that names your operation is the action field. The
proxy accepts exactly nine actions — anything else is rejected with HTTP 400 before it ever
reaches Odoo:
| Action | What it does |
|---|---|
search_count | Count records matching a domain. |
search | Return the ids matching a domain. |
read | Read fields for a list of ids. |
fields_get | Describe a model's fields. |
search_read | Search and read in one call. |
create | Create one or more records. |
write | Update records by id. |
unlink | Delete records by id. |
call_method | Invoke any other model method, named by fn_name. |
The first eight are the CRUD-and-query core. The ninth, call_method, is the escape hatch: any
model method your user can 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. That single action is
why the tiny endpoint list loses nothing in capability. See it in action in
Calling Odoo model methods with call_method.
A worked request
Here is a complete round trip: a search_read that returns five companies. Notice that the model and
the 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": "endpoints-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>"
}
}'Every request is the same five-part shape:
action— one of the nine above.model_id— the Odoo model, e.g.res.partnerorsale.order.params— a JSON array of positional arguments (default[]). Forsearch_readthe first positional argument is the domain, which is why it is wrapped one level deep:[[["is_company", "=", true]]].keyword— a JSON object of keyword arguments (default{}) — herefieldsandlimit.id— a string you choose; it is echoed back so you can correlate responses with requests.
On success you get HTTP 200 and a JSON-RPC envelope whose result holds Odoo's data:
{
"jsonrpc": "2.0",
"id": "endpoints-demo-1",
"result": [
{ "id": 9, "name": "Gemini Furniture", "email": "info@gemini.example" },
{ "id": 14, "name": "Azure Interior", "email": "hello@azure.example" }
]
}Want to change the operation? Change action and model_id. The endpoint stays exactly the same.
The endpoints that aren't the data API
A few more routes exist, and it is worth knowing they are not where your data calls go:
POST /api/odoo/version— returns the target Odoo instance's public version banner. It needs the instance URL but no Odoo credentials, which makes it a handy connectivity check.GET /_/license,GET /_/about,GET /_/metrics— operational endpoints for license status, build info, and Prometheus metrics. None require thex-api-key. Treat these as ops tooling, separate from the data API — don't wire them into your integration's call path.
The HTTP 200 trap
There is one rule that trips up 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 (-32000); an action
outside the allowlist returns 400 (-32001); an upstream timeout returns 504 (-32003).
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 — Odoo's logic error is passed
straight through with a 200 and a populated error object:
{
"jsonrpc": "2.0",
"id": "endpoints-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 full two-layer model, with every error code and a robust client, is in Odoo API error handling.
Where to go next
The takeaway is worth internalizing: Odoo has almost no endpoints, and that is the point. One universal method, one route through the proxy, and nine actions cover the entire ERP.
- Get the credentials right in How to authenticate to the Odoo API.
- See the full transport picture in The Odoo External API: a JSON-RPC integration guide.
- Reach any method beyond the core eight with call_method.
- Read the complete request and response contract in the API reference, or skip the envelope boilerplate with the Python SDK.