Error Codes
The ODXProxy JSON-RPC error catalog for v1 and v2, the two-step success check, and what is safe to retry.
Every /api/*, /v1/*, and /v2/* response is a JSON-RPC 2.0 envelope. result
and error are mutually exclusive:
{
"jsonrpc": "2.0",
"id": "<echoed request id>",
"result": "<any> | omitted",
"error": { "code": 0, "message": "...", "data": "<any> | omitted" }
}Codes 0 and below are the proxy's. Codes from 100 to 599 are Odoo's
HTTP statuses, passed through on v2. You can rely on that split.
Catalog
| HTTP | JSON-RPC code | API | When |
|---|---|---|---|
| 200 | Odoo's own code | v1 | Odoo logic error (pass-through; data carries Odoo's payload). Odoo 19+ uses code 0 here; older versions use 200. |
| 200 | 401 | v2 | Odoo rejected its API key: invalid, expired, wrong scope, or a password. |
| 200 | 403 | v2 | Access rights, or a private (_-prefixed) method. |
| 200 | 404 | v2 | Unknown model or method, or the record doesn't exist. |
| 200 | 409 | v2 | Odoo lock conflict. |
| 200 | 422 | v2 | Validation or user error, or bad arguments (an unknown kwarg, ids on an @api.model method). |
| 200 | 500 | v2 | Odoo server error. |
| 200 | -32006 | v2 | JSON-2 isn't available: Odoo 18 or older (use v1), or the database isn't selectable on that host (dbfilter). |
| 400 | -32001 | v1 | action not in the allowlist. |
| 400 | -32002 | v1 | call_method without fn_name. |
| 400 | -32007 | v2 | model_id or method isn't a valid Odoo identifier, or db / api_key can't go in an HTTP header. Odoo isn't contacted. |
| 401 | -32000 | both | Missing/wrong x-api-key (id is null). |
| 403 | 0 | both | Proxy license expired/invalid. |
| 502 | -32004 | both | Network failure reaching Odoo. |
| 504 | -32003 | both | Upstream Odoo call timed out. |
| 500 | -32005 | both | Internal proxy error decoding Odoo's response. |
A request body that doesn't match the schema at all (for example, v2 kwargs that
isn't a JSON object) gets HTTP 422 with a plain-text message, not an envelope.
Do not infer success from the HTTP status alone
A 200 OK response can still contain an error object — Odoo logic errors
(validation, access-right denial) are returned this way. Clients must:
- If the HTTP status is not 200, treat it as a proxy-level failure and surface
the JSON-RPC
error. - If the status is 200, check whether
erroris set. If so, surface it; otherwise readresult.
Codes that look alike
- Two kinds of 401. HTTP 401 with
-32000means the proxy key (x-api-key) is wrong — a deployment problem. HTTP 200 with code401means Odoo rejected the Odoo user's key — usually because a non-admin key expired, so the end user needs to issue a new one. Handle them separately. - Code
0is a license error only on HTTP 403. Odoo 19+ also uses code0for every v1 Odoo error, on HTTP 200. Classify by HTTP status first. - Read
data, notmessage, on v1 Odoo errors. v1's top-levelmessageis justOdoo Server Error. On both versionserror.datais Odoo's error object{ name, message, arguments, context, debug }:data.messageis the readable text anddata.namethe exception class (e.g.odoo.exceptions.ValidationError) to branch on. Never parsedata.debug— it's a traceback and may be empty.
What to retry
| Code | Retry? |
|---|---|
-32004 (proxy couldn't reach Odoo) | Yes, with backoff. |
409 (Odoo lock conflict) | Yes, with backoff. |
-32003 (timeout) | Only for idempotent calls such as reads. The proxy stopped waiting, but Odoo may still have run the call — retrying a create can duplicate it. |
| Everything else | No. Fix the request, the configuration, or the data first. |
The official SDKs map these codes to typed errors and never retry on their own.