← Blog
odooapiconnectionjson-rpcauthentication

How to Connect to the Odoo API: A Practical Guide

Everything you need to connect to the Odoo API: the four instance coordinates, the two API keys, a first version smoke-test call, and reading a failed connection.

ODXProxy Team · Jul 9, 2026 · 6 min read

How to Connect to the Odoo API: A Practical Guide — ODXProxy blog cover

Before you can read a single record, you have to establish an Odoo API connection — and most of the time people get stuck here, on a 401 or a hung request, is because a connection to Odoo needs more than a URL. This guide walks the whole thing end to end: the four coordinates that identify an Odoo instance, the two separate API keys involved, a zero-risk first call to prove the pipe is open, and how to read the handful of failures that mean "not connected." Everything runs over Odoo's JSON-RPC External API through a single proxy endpoint.

What a connection actually needs

A working connection is really two hops: your code connects to the proxy, and the proxy connects to your Odoo instance. Each hop has its own credential, and the Odoo hop needs four coordinates to locate the exact database and user to act as:

  • url — the base URL of the Odoo server, e.g. https://erp.example.com.
  • db — the Odoo database name. A server can host several; you must name one.
  • user_id — the integer user id (uid) to act as. Not the login string — the numeric id.
  • api_key — that Odoo user's API key (generated in Odoo under the user's preferences).

Those four travel together in every request, inside an odoo_instance object. The proxy itself is guarded by its own key:

  • x-api-key — the proxy's static key, sent as an HTTP header. This is not the Odoo user's key; conflating the two is the single most common connection mistake.
Two keys, two layers. x-api-key gets you past the proxy. The api_key inside odoo_instance gets the proxy into Odoo as a specific user. A request needs both, and a mix-up in either returns an authentication error — see below for telling them apart.

The full walkthrough of generating the Odoo user key lives in how to authenticate to the Odoo API.

Prove the pipe is open first

The best way to test a connection is a call that needs almost nothing — so a failure points at exactly one thing. The proxy exposes a version endpoint that only needs the proxy key and the target Odoo URL, no user credentials at all. It's the ideal smoke test:

{
  "id": "conn-check-1",
  "url": "https://erp.example.com"
}

Send that to the version route with your proxy key in the header:

POST /api/odoo/version
x-api-key: <the proxy API key>

A healthy connection comes back as a JSON-RPC envelope whose result is Odoo's version_info:

{
  "jsonrpc": "2.0",
  "id": "conn-check-1",
  "result": { "server_version": "18.0", "server_version_info": [18, 0, 0, "final", 0] }
}

If that succeeds, you know two things at once: your proxy key is valid and the proxy can physically reach the Odoo server. Only after that do you introduce the database and user credentials.

Your first authenticated call

With the pipe proven, make a real read against the main execute endpoint. Reading one partner is a good first authenticated call — it exercises the db, user_id, and api_key together:

{
  "id": "conn-read-1",
  "action": "search_read",
  "model_id": "res.partner",
  "params": [[["is_company", "=", true]]],
  "keyword": { "fields": ["name", "email"], "limit": 1 },
  "odoo_instance": {
    "url": "https://erp.example.com",
    "db": "prod",
    "user_id": 2,
    "api_key": "<the Odoo user API key>"
  }
}
POST /api/odoo/execute
x-api-key: <the proxy API key>

A single partner in result means the full connection — proxy key, database, user, and user key — is working end to end. From here, the other eight actions (search, read, create, write, unlink, search_count, fields_get, and call_method) all use the exact same envelope; only action and the params/keyword payload change. That shape is laid out in the one route you actually call.

Reading a failed connection

When a connection doesn't work, the error tells you which hop broke. The two layers report differently, and knowing which is which saves hours:

  • Wrong or missing proxy key → HTTP 401, error code -32000. Your x-api-key header is absent or incorrect. This never reaches Odoo.
  • Proxy can't reach Odoo → HTTP 502, error code -32004 (bad gateway). The URL is wrong, DNS fails, or Odoo is down. If the version smoke test above fails this way, fix networking before touching credentials.
  • Odoo took too long → HTTP 504, error code -32003 (timeout). The call reached Odoo but didn't finish inside the window. Tune it with the x-request-timeout header (integer seconds; default 15).
  • Bad Odoo credentials → HTTP 200 with a populated error. This is the one that surprises people: an authentication failure inside Odoo (wrong db, user_id, or the Odoo api_key) is an Odoo-layer logic error, so it rides back on a 200, not a 401.
A 200 is not a green light. Odoo logic errors — including a wrong database or user key — return HTTP 200 with an error object populated. Always check the HTTP status and then the body's error field before you treat a connection as established. Only proxy-layer failures use non-200 codes.

That two-step check — status first, then error — is the habit that separates a robust client from one that silently treats failures as success. The complete code-by-code map and retry guidance is in Odoo API error handling.

Keeping the connection healthy

A connection isn't just "does it work once" — it's "does it stay usable under real conditions":

  • Set a sane timeout. Big reads and heavy call_method calls can outrun the 15-second default. Send x-request-timeout with a larger integer (in seconds) on those calls rather than raising it globally.
  • Reuse credentials, not connections. There's no session to keep alive — each request carries its own odoo_instance, so the proxy is effectively stateless from your side. Store the four coordinates securely and send them each time.
  • Give every request an id. The id you send is echoed back in the response, which makes matching replies to requests (and log correlation) trivial when you're firing many calls.

Where to go next