
Odoo
Connect your Odoo CRM and your voice agents can recognise the caller, read their open leads, keep the contact current, and file answered calls on the contact, all during the call in your own Odoo. Works with Odoo 16 through 19.
How it works
Connect your Odoo instance once, choose which agents may use it, and give each one a read or a write grant. Aethex stores the credential encrypted and turns your CRM into a small set of actions an agent can call mid-call, choosing for itself when each is needed. The grant is the whole story: read lets an agent look things up, write lets it change your CRM and have its answered calls filed for you. Aethex talks to whichever version you run, so the same actions work the same way whether your instance is Odoo 16, 17, 18, or 19.
Supported versions
Aethex speaks to each Odoo version over its own API. Odoo 19 uses the new External JSON-2 API; Odoo 16, 17, and 18 use the legacy JSON-RPC API. You pick the version, or let Aethex detect it, when you connect, and the connection records the version and transport it settled on so you can confirm it.
| Odoo version | Transport | What you send |
|---|---|---|
| 19 | External JSON-2 | database, API key |
| 18 | JSON-RPC | database, username, API key or password |
| 17 | JSON-RPC | database, username, API key or password |
| 16 | JSON-RPC | database, username, API key or password |
config.odoo_major_version to auto, 16, 17, 18, or 19. auto reads your server's reported version and picks the transport for you. Versions below 16 and untested versions above 19 are refused when you connect.Connect your Odoo instance
Send the credential to POST /integrations. Aethex validates it against your instance before storing anything, so a wrong key, database, or version comes back as an error on this call rather than as a silent failure mid-call. Every connection needs your instance URL in base_url (public HTTPS only), the database name in config.database, the credential in access_token, and auth_type set to api_key. Odoo 19 identifies the API user by the key itself, so you do not send a username. Odoo 16, 17, and 18 authenticate with a login, so you also send config.username; the credential can be that user's API key or password. Not sure which version you run? Set odoo_major_version to auto and include the username.
# Odoo 19 (JSON-2): no username, the API key identifies the user
curl -X POST https://api.aethexai.com/api/v1/integrations \
-H "X-API-Key: $AETHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "odoo",
"auth_type": "api_key",
"base_url": "https://your-company.odoo.com",
"access_token": "<your-odoo-api-key>",
"display_name": "Sales CRM",
"config": {
"database": "your-company",
"odoo_major_version": 19
}
}'
# Odoo 16, 17, 18 (or "auto"): also send the user's login
curl -X POST https://api.aethexai.com/api/v1/integrations \
-H "X-API-Key: $AETHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "odoo",
"auth_type": "api_key",
"base_url": "https://crm.your-company.com",
"access_token": "<your-odoo-api-key-or-password>",
"display_name": "Sales CRM",
"config": {
"database": "your-company",
"username": "[email protected]",
"odoo_major_version": "auto"
}
}'Grant it to agents
Connecting stores the credential; it does not hand it to anything. A connection with no agent is inert: no call sees its tools and nothing is written to your CRM. Grant it per agent, in the connect call or afterwards with PUT /integrations/{id}/agents. That PUT replaces the whole set, so send every agent that should keep access, not just the one you are adding. Start at read; move an agent to write only when you want it changing records and filing calls.
curl -X PUT https://api.aethexai.com/api/v1/integrations/$CONNECTION_ID/agents \
-H "X-API-Key: $AETHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agents": [
{ "agent_id": "<agent-uuid>", "access": "read" }
]
}'What an agent can do
What an agent can reach is decided when the call is assembled: an action outside its grant is never offered to the model, so a read-only agent has no way to change anything. These are the tools, and the grant each one needs.
| Tool | What it does | Grant |
|---|---|---|
odoo_lookup_contact | Finds the caller by exact phone or email and returns their open CRM leads in one result, so the agent can greet them by name and pick up where the last conversation left off. Matches on both the phone and mobile fields. | read |
odoo_list_stages | Lists your CRM stages, optionally for one sales team, so the agent knows the pipeline it is working in. | read |
odoo_record_caller | Updates the one matched contact, or creates a new contact when none matches. Returns a disambiguation list rather than guessing when more than one contact matches. | write |
odoo_move_lead_stage | Moves one lead or opportunity to an unambiguous stage, by id or exact label. Off by default; enable it per connection with config.enabled_tools. | write |
odoo_move_lead_stage is opt-in. To turn it on, send config.enabled_tools with the full list of tools you want the connection to expose (it is an exact allowlist, not an addition to the defaults). Include odoo_log_call in that list if you want automatic call logging to keep working.Automatic call logging
When a call ends, Aethex looks the caller up and files it on their Odoo chatter as an internal note: the call id, direction, start time, duration, and outcome, plus a short summary. It needs no tool call and no prompt.
Import contacts as call lists
An Odoo connection can also seed call lists from your contacts, without exposing your whole database. List what you can import with GET /integrations/{id}/imports/entities, see the safe fields with GET /integrations/{id}/imports/fields?entity=res.partner, preview a validated sample with POST /integrations/{id}/imports/preview, and read paged rows with POST /integrations/{id}/imports/page. Sensitive fields such as bank details, tokens, and national ids are never offered, and the flow is read-only until you run the import.
Security
Your Odoo instance must be reachable over public HTTPS; private, loopback, and internal-only hosts are refused, and the hostname is resolved and pinned immediately before each request to close DNS-rebinding gaps. Redirects are not followed, requests time out at 15 seconds, and responses are capped. During a call the agent still answers within 9 seconds whatever Odoo does. The credential is encrypted at rest and never returned by any endpoint, and neither is the database name or username. Read and write grants are enforced per agent, and your Odoo user's own access rules still apply on top, so a grant can only narrow what that user could already do.
Errors
Connect and tool calls surface these statuses.
Odoo rejected the API key or password.
Check the credential, the username (Odoo 16 to 18), and the database, then reconnect.
The Odoo user is not allowed the operation or record.
Give the integration user CRM read or write rights in Odoo, then retry.
The instance does not expose the required model or method.
Confirm CRM is installed and the version is one Aethex supports.
Unknown provider, a version that does not match the server, an unsupported version, a missing username for Odoo 16 to 18, a username sent for Odoo 19, or an unsupported config field.
Read the message; it names the specific problem.
The per-tenant connect or import rate limit was exceeded.
Back off and retry after the Retry-After window.
Odoo was unreachable, the encryption key is not configured, or the rate limiter is offline.
Transient; retry shortly.
Notes and limits
A few things worth knowing before you plan around them.
| Limit | Detail |
|---|---|
| One connection per workspace | A workspace holds one active Odoo connection, so connecting a second instance replaces the first and takes its agent grants with it. |
| Phone is not a unique key | Aethex matches on the phone and mobile fields using both the E.164 form and the number as dialed, and returns ambiguous matches as candidates for the agent to disambiguate rather than guessing. |
| Optional fields are detected once | The version and the optional fields your database exposes are detected at connect and cached on the connection. Reconnect to pick up a field you add later, such as after installing a module. |
| Version is recorded | The detected version and transport are stored on the connection and returned by GET /integrations/{id}, so you can confirm what Aethex is talking to. |
| Recording is not an atomic upsert | Recording a caller is a search-then-write, so two concurrent callers with the same new number can create duplicate contacts. |
