Odoo Module
Work with Odoo from a workflow: create, read, update, delete, search and count records of any model (contacts, leads, sale orders, invoices, projects, tasks, employees, your own models) and call any model method, through Odoo's standard JSON-RPC external API.
Typical uses: create a lead from a chat or a form, look up a customer's open orders by email, confirm a sale order after a payment, post a note on a lead when a call ends, count overdue invoices for a daily report, or feed Odoo records to an AI task.
Works with Odoo Online and on-premise, version 12 and later (API keys from version 14).
Requirements
- The address of your Odoo server and the name of its database.
- An Odoo user for the workflow, with the access rights it needs. A dedicated integration user is recommended: every record the workflow creates or changes is done by that user, and nothing beyond its rights is possible.
- An API key for that user: in Odoo, Preferences → Account Security → New API Key. On Odoo 13 and older, the user's password is used instead.
Configuration
| Setting | Required | Notes |
|---|---|---|
| Odoo address | yes | https://mycompany.odoo.com for Odoo Online, or the public address of an on-premise server (a path such as https://erp.example.com/odoo is fine). https is strongly recommended: the key travels with every call. |
| Database | yes | On Odoo Online, usually the first part of the address (mycompany). On-premise: the name on the login page. |
| User (login) | yes | The login of the user the workflow acts as, usually its email. |
| API key | yes | The user's API key (or password on old versions). Stored securely. |
| Default timeout (seconds) | no | 5–300. Empty: 60. A task can set its own. |
The module authenticates once per environment and reuses the user id; a refused key is reported on the task that hit it.
Values and types
Odoo is strict about types, so the module keeps them:
- Fields rows (Create, Update): a value that is exactly one
{variable}keeps the variable's type (number, yes/no, list, object). Otherwise, with the default Auto type, a plain whole number (42), a decimal (12.5),trueorfalseis typed and anything else is text. Pick Text to force text (a zip code such as00100, a phone number), Number, Yes/no, or JSON for a relation command or a nested value. - A many2one field (
partner_id,user_id,stage_id) takes the record id. A many2many takes a command list in JSON, e.g.[[6, 0, [1, 2]]]to set tags 1 and 2; a one2many takes[[0, 0, {"product_id": 7, "product_uom_qty": 2}]]to add a line. - JSON fields (Domain, Arguments, Keyword arguments, Context): a
{variable}inside quotes becomes text, safely escaped ("ilike", "{email}"); a{variable}outside quotes becomes the variable's own JSON value ("in", {ids}sends the list,">", {minimum}the number; a missing variable isnull). - Dates from the workflow are sent as Odoo expects them:
yyyy-MM-dd HH:mm:ssin UTC. - Records come back as Odoo sends them: an empty field is
false(not null), a many2one is[id, name]({partner.country_id.0}is the id,{partner.country_id.1}the name), a one2many or many2many is a list of ids.
Odoo record
Create, read, update or delete records of one model. The result goes to the Output variable.
| Property | Notes |
|---|---|
| Operation | Create, Read, Update or Delete. |
| Model | Technical name: res.partner (contacts), crm.lead (leads), sale.order, sale.order.line, account.move (invoices), product.product, project.task, hr.employee, x_my_model... |
| Record ID | Read, Update, Delete. One id, several separated by commas, or a {variable} holding an id, a list of ids, a record read earlier (its id is used) or a many2one value such as {order.partner_id}. |
| Fields | Create, Update. One row per field: name, value (or {variable}), type (see Values and types). |
| Fields to read | Read. Comma-separated field names. Empty: every field of the model, which is slow and large on models such as sale.order. Name what you need. |
Output:
- Create: the new record's id (
{partnerId}). - Read with one id: the record (
{partner.name},{partner.email}). With several ids: a list of records ({partners.0.name}). - Update, Delete:
true.
Advanced: Context (JSON, e.g. {"lang": "it_IT"} for translated names, {"active_test": false} to reach archived records, {"tracking_disable": true} to skip chatter messages) and Timeout.
Odoo search
Find records with a domain filter, or count them. Both write {found.records} and {found.count}.
| Property | Notes |
|---|---|
| Operation | Search (search_read) or Count (search_count). |
| Model | Technical name, as above. |
| Domain | Odoo's filter as a JSON list of [field, operator, value] conditions. Empty: every record. Operators: =, !=, >, >=, <, <=, in, not in, like, ilike, =like, =ilike, child_of. Conditions are combined with AND; put `" |
| Fields | Search. Comma-separated field names each record carries. Empty: every field. |
| Maximum records | Search. 1–1000. Empty: 80. |
| Skip | Search. Records to skip before the first returned (paging). |
| Order | Search. create_date desc, amount_total desc, name. Empty: the model's default. |
Examples:
[["email", "=ilike", "{email}"]]
[["partner_id", "=", {partnerId}], ["state", "in", ["sale", "done"]]]
["|", ["stage_id.name", "=", "New"], ["stage_id.name", "=", "Qualified"]]
[["invoice_date_due", "<", "{today}"], ["payment_state", "=", "not_paid"], ["move_type", "=", "out_invoice"]]
Output: {found.count} is how many records were returned (Search) or match (Count); {found.records} the list, each with its fields ({found.records.0.name}), empty for Count.
Advanced: Context and Timeout, as above.
Odoo method
Call any public method of a model: the escape hatch for what the two tasks above do not cover.
| Property | Notes |
|---|---|
| Model | Technical name. |
| Method | A public method (Odoo refuses names starting with _ over the API): action_confirm, action_post, message_post, name_search, action_set_won, a method of your own module. |
| Record ID | Optional. The record(s) the method runs on, sent as the first positional argument. Leave empty for model-level methods. |
| Arguments | Further positional arguments as a JSON list, e.g. ["{query}"]. |
| Keyword arguments | Named arguments as a JSON object, e.g. {"body": "{note}", "message_type": "comment"}. |
Output: whatever the method answers (a value, a list, an object, or nothing: many actions answer true or nothing).
Common calls:
| Goal | Model | Method | Record ID | Keyword arguments |
|---|---|---|---|---|
| Confirm a quotation | sale.order | action_confirm | {orderId} | |
| Post a note in the chatter | crm.lead (any model with a chatter) | message_post | {leadId} | {"body": "{note}"} |
| Mark a lead won | crm.lead | action_set_won | {leadId} | |
| Post an invoice | account.move | action_post | {invoiceId} | |
| Find by display name | res.partner | name_search | Arguments ["{query}"], Keyword arguments {"limit": 5} |
Advanced: Context (added to, and overriding, a context given in Keyword arguments) and Timeout.
Errors
A task takes its error flow when the input is invalid (a field or JSON that cannot be read, a bad id), the server cannot be reached or does not answer JSON-RPC (a wrong address, a proxy login in front of Odoo), the user or API key is refused, the user lacks a right (AccessError), a record does not exist (MissingError), Odoo refuses the data (ValidationError, UserError, with Odoo's own message), or the call times out. The log says which, in Odoo's words where it has them.
A model or field Odoo does not know is reported as such: check the technical name and that the app that defines it is installed.
Example: lead from a chat, note on hand-off
- Odoo search on
crm.lead, Domain[["email_from", "=ilike", "{email}"]], Fieldsname, stage_id, output{leads}. - If
{leads.count}is 0: Odoo record Create oncrm.leadwithname={company} - website chat,email_from={email},partner_name={company}→{leadId}; otherwise{leads.records.0.id}. - Odoo method
message_postoncrm.lead, Record ID{leadId}, Keyword arguments{"body": "Chat transcript:<br/>{transcript}"}.