Odoo Manager
Manage Odoo (contacts, any business objects, and metadata) via the official External XML-RPC API. Supports generic CRUD operations on any model using execute_kw, with ready-made flows for res.partner
Manage Odoo (contacts, any business objects, and metadata) via the official External XML-RPC API. Supports generic CRUD operations on any model using execute_kw, with ready-made flows for res.partner
Real data. Real impact.
Growing
Developers
Per week
Open source
Skills give you superpowers. Install in 30 seconds.
Odoo server URL precedence (highest to lowest):
temporary_url — one-time URL for a specific operationuser_url — user-defined URL for the current sessionODOO_URL — environment default URLThis allows you to:
Examples (conceptual):
// Default: uses ODOO_URL from environment {{resolved_url}}/xmlrpc/2/common// Override for one operation: temporary_url = "https://staging.mycompany.odoo.com" {{resolved_url}}/xmlrpc/2/common
// Override for session: user_url = "https://client-xyz.odoo.com" {{resolved_url}}/xmlrpc/2/common
Database name (
db) precedence:
temporary_dbuser_dbODOO_DBUse this to:
Username precedence:
temporary_usernameuser_usernameODOO_USERNAMESecret (password or API key) precedence:
temporary_api_key or temporary_passworduser_api_key or user_passwordODOO_API_KEY (if set) or ODOO_PASSWORDImportant:
Environment variables are handled via standard OpenClaw metadata:
requires.env declares required variables (ODOO_URL, ODOO_DB, ODOO_USERNAME, ODOO_PASSWORD). ODOO_API_KEY is an optional environment variable used instead of the password when present; it is not listed in metadata and should simply be set in the environment when needed.
At runtime the skill always works with:
{{resolved_url}} — final URL{{resolved_db}} — final database name{{resolved_username}} — final login{{resolved_secret}} — password or API key actually used to authenticateThese are computed using the precedence rules above.
The
andtemporary_*names are runtime context variables used by the skill logic, not OpenClaw metadata fields. OpenClaw does not have anuser_*metadata key; context is resolved dynamically at runtime as described below.optional.context
User examples:
odoo_demo juste pour cette opération"Behavior:
temporary_* (url, db, username, api_key/password)This is ideal for:
User examples:
clientx_prod pour cette session"Behavior:
user_* (url, db, username, api_key/password)temporary_* or by clearing user_*User examples:
Action:
user_url, user_db, user_username, user_password, user_api_keyODOO_URL, ODOO_DB, ODOO_USERNAME, ODOO_PASSWORD / ODOO_API_KEY)User examples:
Response should show (never full secrets):
Current Odoo Context: - URL: https://client-xyz.odoo.com (user_url) - DB: clientxyz_prod (user_db) - Username: api_integration (user_username) - Secret: using API key (user_api_key) - Fallback URL: https://default.odoo.com (ODOO_URL) - Fallback DB: default_db (ODOO_DB)
Odoo exposes part of its server framework over XML-RPC (not REST). The External API is documented here: https://www.odoo.com/documentation/18.0/fr/developer/reference/external_api.html
Two main endpoints:
{{resolved_url}}/xmlrpc/2/common — authentication and meta calls{{resolved_url}}/xmlrpc/2/object — model methods via execute_kwCall
version() on the common endpoint to verify URL and connectivity:
common = xmlrpc.client.ServerProxy(f"{resolved_url}/xmlrpc/2/common") version_info = common.version()
Example result:
{ "server_version": "18.0", "server_version_info": [18, 0, 0, "final", 0], "server_serie": "18.0", "protocol_version": 1 }
Use
authenticate(db, username, password_or_api_key, {}) on the common endpoint:
uid = common.authenticate(resolved_db, resolved_username, resolved_secret, {})
uid is an integer user ID and will be used in all subsequent calls.
If authentication fails,
uid is False / 0 — the skill should:
ODOO_URL, ODOO_DB, username, and secretBuild an XML-RPC client for the
object endpoint:
models = xmlrpc.client.ServerProxy(f"{resolved_url}/xmlrpc/2/object")
Then use
execute_kw with the following signature:
models.execute_kw( resolved_db, uid, resolved_secret, "model.name", # e.g. "res.partner" "method_name", # e.g. "search_read" [positional_args], {keyword_args} )
All ORM operations in this skill are expressed in terms of
execute_kw.
Domains are lists of conditions:
domain = [["field_name", "operator", value], ...]
Examples:
[['is_company', '=', True]][['country_id', '=', france_id]][['probability', '>', 50]]Common operators:
"=", "!=", ">", ">=", "<", "<=""like", "ilike" (case-insensitive)"in", "not in""child_of" (hierarchical relations)YYYY-MM-DD or ISO 8601 format.int) when writing; reads often return [id, display_name].Each subsection below shows typical user queries and the corresponding
execute_kw usage. They are applicable to any model (not only res.partner).
User queries:
Action (generic):
ids = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "search", [domain], {"offset": 0, "limit": 80} )
Notes:
domain is a list (can be empty [] to match all records).offset and limit for pagination.User queries:
Action:
count = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "search_count", [domain] )
User queries:
Action:
records = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "read", [ids], {"fields": ["name", "country_id", "comment"]} )
If
fields is omitted, Odoo returns all readable fields (often a lot).
Shortcut for
search() + read() in a single call.
User queries:
Action:
records = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "search_read", [domain], { "fields": ["name", "country_id", "comment"], "limit": 5, "offset": 0, # Optional: "order": "name asc" } )
User queries:
Action:
new_id = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "create", [{ "name": "New Partner" # other fields... }] )
Returns the newly created record ID.
User queries:
Action:
success = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "write", [ids, {"field": "new value", "other_field": 123}] )
Notes:
ids is a list of record IDs.ids receive the same values.User queries:
Action:
success = models.execute_kw( resolved_db, uid, resolved_secret, "model.name", "unlink", [ids] )
Useful for quick lookup on models with a display name (e.g. partners, products).
User queries:
Action:
results = models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "name_search", ["Agrolait"], {"limit": 10} )
Result is a list of
[id, display_name].
res.partner is the core model for contacts, companies, and many business relations in Odoo.
User queries:
Action:
companies = models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "search_read", [[["is_company", "=", True]]], {"fields": ["name", "country_id", "comment"], "limit": 80} )
User queries:
Action:
[partner] = models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "read", [[7]], {"fields": ["name", "country_id", "comment"]} )
User queries:
Minimal body:
partner_id = models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "create", [{ "name": "New Partner", "is_company": True }] )
Additional fields examples:
street, zip, city, country_idemail, phone, mobilecompany_type ("person" or "company")User queries:
Action:
models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "write", [[7], { "street": "New street 1", "phone": "+33 1 23 45 67 89" }] )
User queries:
Action:
models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "unlink", [[999]] )
User queries:
Action:
fields = models.execute_kw( resolved_db, uid, resolved_secret, "res.partner", "fields_get", [], {"attributes": ["string", "help", "type"]} )
The result is a mapping from field name to metadata:
{ "name": {"type": "char", "string": "Name", "help": ""}, "country_id": {"type": "many2one", "string": "Country", "help": ""}, "is_company": {"type": "boolean", "string": "Is a Company", "help": ""} }
User queries:
Action:
models_list = models.execute_kw( resolved_db, uid, resolved_secret, "ir.model", "search_read", [[]], {"fields": ["model", "name", "state"], "limit": 200} )
state indicates whether a model is defined in code ("base") or created dynamically ("manual").
User queries:
Action (simplified):
partner_model_ids = models.execute_kw( resolved_db, uid, resolved_secret, "ir.model", "search", [[["model", "=", "res.partner"]]] ) fields_meta = models.execute_kw( resolved_db, uid, resolved_secret, "ir.model.fields", "search_read", [[["model_id", "in", partner_model_ids]]], {"fields": ["name", "field_description", "ttype", "required", "readonly"], "limit": 500} )
authenticate returns False or later calls fail.xmlrpc/2/common or xmlrpc/2/object.The skill should:
limit / offset on search and search_read to handle large datasets.limit to a reasonable value (e.g. 80).fields list for read / search_read when possible.=, in) for large datasets.{{resolved_url}}, {{resolved_db}}, {{resolved_username}}, {{resolved_secret}}version() on {{resolved_url}}/xmlrpc/2/commonuidexecute_kw on res.partner with search_read and domain [['is_company', '=', True]]common.authenticatecreate a new res.partner with {"name": "New Partner", "is_company": True}read that ID with fields ["name", "is_company", "country_id"]temporary_url and/or temporary_db to point to another Odoo environment.This skill can:
search, search_count, read, search_read, create, write, unlink) on any Odoo model via execute_kw.res.partner (contacts / companies).fields_get, ir.model, and ir.model.fields.No automatic installation available. Please visit the source repository for installation instructions.
View Installation Instructions1,500+ AI skills, agents & workflows. Install in 30 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.