Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/odoo/documentation/llms.txt

Use this file to discover all available pages before exploring further.

The Odoo XML-RPC API provides programmatic access to most ORM model methods over HTTP using the XML-RPC protocol. It exposes two main endpoints: /xmlrpc/2/common for unauthenticated operations (version check, login), and /xmlrpc/2/object for authenticated model method calls.
Access to the external API is only available on Custom Odoo pricing plans. It is not available on One App Free or Standard plans. Visit the Odoo pricing page for details.
Deprecated since Odoo 19.0. The XML-RPC and JSON-RPC APIs at /xmlrpc, /xmlrpc/2, and /jsonrpc are scheduled for removal in Odoo 22 (fall 2028) and Online 21.1 (winter 2027). Migrate new integrations to the External JSON-2 API.Note: internal @route(type='jsonrpc') controllers are not deprecated.

Configuration

For Odoo Online instances (<domain>.odoo.com), users need a local password set before XML-RPC access is possible: go to Settings → Users & Companies → Users, select the user, click Action → Change Password.
url = "<insert server URL>"
db = "<insert database name>"
username = "admin"
password = "<insert password>"

Using API Keys

From Odoo 14, API keys can replace the password in all XML-RPC calls. Generate one via Preferences → Account Security → New API Key, then substitute it wherever the password argument appears.

Test Database

For exploration, request a temporary test database from demo.odoo.com:
import xmlrpc.client
info = xmlrpc.client.ServerProxy("https://demo.odoo.com/start").start()
url, db, username, password = info["host"], info["database"], info["user"], info["password"]

Authentication

Check Server Version (No Auth Required)

import xmlrpc.client
common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common")
common.version()
Result:
{
    "server_version": "13.0",
    "server_version_info": [13, 0, 0, "final", 0],
    "server_serie": "13.0",
    "protocol_version": 1
}

Authenticate

Call authenticate to get the user’s integer ID (uid), required for all object service calls:
uid = common.authenticate(db, username, password, {})

Calling Model Methods

All model access goes through execute_kw on /xmlrpc/2/object:
execute_kw(db, uid, password, model, method, args, kwargs={})
ParameterDescription
dbDatabase name
uidUser ID from authenticate
passwordUser password or API key
modelModel technical name, e.g. res.partner
methodMethod name, e.g. search, read, create
argsList of positional arguments
kwargsDict of keyword arguments (optional)

CRUD Operations

Search Records

models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object")

# Returns list of matching record IDs
ids = models.execute_kw(db, uid, password,
    "res.partner", "search",
    [[["is_company", "=", True]]],
    {"limit": 10}
)
$models = ripcord::client("$url/xmlrpc/2/object");
$ids = $models->execute_kw($db, $uid, $password,
    "res.partner", "search",
    array(array(array("is_company", "=", true))),
    array("limit" => 10)
);

Read Records

records = models.execute_kw(db, uid, password,
    "res.partner", "read",
    [ids],
    {"fields": ["name", "country_id", "comment"]}
)

search_read (Combined)

records = models.execute_kw(db, uid, password,
    "res.partner", "search_read",
    [[["is_company", "=", True]]],
    {"fields": ["name", "country_id"], "limit": 5}
)

Count Records

Use search_count to retrieve only the number of records matching a domain, without fetching the records themselves. It takes the same domain filter as search:
count = models.execute_kw(db, uid, password,
    "res.partner", "search_count",
    [[["is_company", "=", True]]]
)
Calling search and then search_count (or vice versa) may not yield coherent results if other users are modifying the database between the two calls.

Create Records

new_id = models.execute_kw(db, uid, password,
    "res.partner", "create",
    [{"name": "New Company", "is_company": True}]
)
new_id = models.execute_kw(db, uid, password,
    "res.partner", "create",
    [{ name: "New Company", is_company: true }]
)

Write (Update) Records

models.execute_kw(db, uid, password,
    "res.partner", "write",
    [[new_id], {"name": "Updated Name"}]
)

Delete Records

models.execute_kw(db, uid, password,
    "res.partner", "unlink",
    [[new_id]]
)

# Verify deletion
remaining = models.execute_kw(db, uid, password,
    "res.partner", "search",
    [[["id", "=", new_id]]]
)
# → []

Inspection and Introspection

ir.model — Querying Models

# List all models with "partner" in their name
models.execute_kw(db, uid, password,
    "ir.model", "search_read",
    [[["name", "ilike", "partner"]]],
    {"fields": ["name", "model"]}
)
Create a dynamic custom model (name must start with x_):
model_id = models.execute_kw(db, uid, password,
    "ir.model", "create",
    [{"name": "Custom Model", "model": "x_custom_model", "state": "manual"}]
)
Custom model names must start with x_. The state field must be set to "manual". It is not possible to add new Python methods via the RPC API — only fields.

ir.model.fields — Adding Custom Fields

field_id = models.execute_kw(db, uid, password,
    "ir.model.fields", "create",
    [{
        "model_id": model_id,
        "name": "x_name",
        "ttype": "char",
        "state": "manual",
        "required": True,
    }]
)

# Use the new custom model and field
record_id = models.execute_kw(db, uid, password,
    "x_custom", "create",
    [{"x_name": "test record"}]
)
result = models.execute_kw(db, uid, password,
    "x_custom", "read",
    [[record_id]]
)

fields_get — Field Metadata

fields = models.execute_kw(db, uid, password,
    "res.partner", "fields_get",
    [],
    {"attributes": ["string", "help", "type"]}
)

Migration Guide to JSON-2

When migrating from XML-RPC to the JSON-2 API:
1

Generate an API key

Create a bearer API key via Preferences → Account Security → New API Key. The key replaces the uid+password pair.
2

Update the URL

Change https://host/xmlrpc/2/object to https://host/json/2/<model>/<method>.
3

Move args to the JSON body

XML-RPC positional args become named JSON keys in the request body. context is always available.
4

Remove db/uid/password from calls

The database is identified by X-Odoo-Database header (when needed). Authentication is via Authorization: bearer <key>. No more per-call credentials.
Before (XML-RPC):
models.execute_kw(db, uid, password, "res.partner", "read",
    [[1, 2, 3]], {"fields": ["name"], "context": {"lang": "en_US"}})
After (JSON-2):
requests.post(
    f"https://{host}/json/2/res.partner/read",
    headers={"Authorization": f"bearer {api_key}"},
    json={"ids": [1, 2, 3], "fields": ["name"], "context": {"lang": "en_US"}},
)

Build docs developers (and LLMs) love