Skip to main content

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​

SettingRequiredNotes
Odoo addressyeshttps://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.
DatabaseyesOn Odoo Online, usually the first part of the address (mycompany). On-premise: the name on the login page.
User (login)yesThe login of the user the workflow acts as, usually its email.
API keyyesThe user's API key (or password on old versions). Stored securely.
Default timeout (seconds)no5–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), true or false is typed and anything else is text. Pick Text to force text (a zip code such as 00100, 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 is null).
  • Dates from the workflow are sent as Odoo expects them: yyyy-MM-dd HH:mm:ss in 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.

PropertyNotes
OperationCreate, Read, Update or Delete.
ModelTechnical 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 IDRead, 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}.
FieldsCreate, Update. One row per field: name, value (or {variable}), type (see Values and types).
Fields to readRead. 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.

Find records with a domain filter, or count them. Both write {found.records} and {found.count}.

PropertyNotes
OperationSearch (search_read) or Count (search_count).
ModelTechnical name, as above.
DomainOdoo'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 `"
FieldsSearch. Comma-separated field names each record carries. Empty: every field.
Maximum recordsSearch. 1–1000. Empty: 80.
SkipSearch. Records to skip before the first returned (paging).
OrderSearch. 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.

PropertyNotes
ModelTechnical name.
MethodA 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 IDOptional. The record(s) the method runs on, sent as the first positional argument. Leave empty for model-level methods.
ArgumentsFurther positional arguments as a JSON list, e.g. ["{query}"].
Keyword argumentsNamed 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:

GoalModelMethodRecord IDKeyword arguments
Confirm a quotationsale.orderaction_confirm{orderId}
Post a note in the chattercrm.lead (any model with a chatter)message_post{leadId}{"body": "{note}"}
Mark a lead woncrm.leadaction_set_won{leadId}
Post an invoiceaccount.moveaction_post{invoiceId}
Find by display nameres.partnername_searchArguments ["{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​

  1. Odoo search on crm.lead, Domain [["email_from", "=ilike", "{email}"]], Fields name, stage_id, output {leads}.
  2. If {leads.count} is 0: Odoo record Create on crm.lead with name = {company} - website chat, email_from = {email}, partner_name = {company} → {leadId}; otherwise {leads.records.0.id}.
  3. Odoo method message_post on crm.lead, Record ID {leadId}, Keyword arguments {"body": "Chat transcript:<br/>{transcript}"}.