Odoo API
The Odoo API: How to Talk to Odoo From Anywhere Else
Odoo has no separate API layer. Anything you can do in the interface, you can do over the wire against the same models and the same access rules. That is unusually powerful, and it means the security of your integration is decided almost entirely by which user account it runs as.
What the Odoo API is, and what it is not
There is no separate Odoo API product. The interface you click is a client, and the endpoints it uses are the endpoints you use. When you call `search_read` on `res.partner` from a script, you are invoking the same method the web client invokes, on the same model, with the same access rules applied.
This has three consequences worth stating plainly.
You can reach everything, including models that were never designed for external access. There is no allow-list of API-safe objects. Whether a call succeeds is decided by the access rights of the user making it, not by any notion of what is public.
There is no separate API versioning. The contract is the model schema, and the model schema changes between Odoo versions. Field renames that are unremarkable inside Odoo become breaking changes for every integration referring to the old name.
And there is no separate rate limit or quota. The limit is your server.
Which endpoint to use in 2026
Odoo has accumulated four ways in.
XML-RPC, at `/xmlrpc/2/common` to authenticate and `/xmlrpc/2/object` to call methods, is the one every tutorial shows. It is stable, it works from any language with an XML-RPC client, and it is verbose.
JSON-RPC, at `/jsonrpc`, does the same thing with JSON payloads.
Both of these are now on notice. The Odoo 19 source logs a deprecation warning on every single call to `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc`, and the message states they are deprecated in Odoo 19 and scheduled for removal in Odoo 22. That is not a rumour or a roadmap slide, it is in the code path that serves the request. If you have integrations on those endpoints, you now have a deadline rather than an open question.
The newer JSON endpoint is what replaces them. It is a single route, `POST /json/2/
Custom HTTP controllers are the fourth option, and a different kind of thing. You write your own routes in a module and decide their shape. Use this when the consumer should not know your model names, when you need a payload that does not map to one model, or when a third party requires a specific contract you cannot change.
Authentication, and the mistake almost everyone makes
Odoo supports authenticating with a username and password, and you should not. Use an API key, generated per user from My Profile and Account Security. Keys can be revoked individually, they do not stop working when someone changes their password, and the newer endpoint expects one in the Bearer header.
The mistake is not usually the credential type. It is the account.
An integration authenticates as a user, and inherits that user's access rights and record rules exactly. Point it at an administrator account, which is the fastest way to make it work, and you have given an external system unrestricted read, write and delete across the database, with no meaningful audit boundary. When something later writes to the wrong records, every log line says it was the administrator.
Create a dedicated user for each integration. Give it only the groups it needs. Name it after the system it serves so the chatter on a record tells you which integration touched it. This costs about ten minutes and is the single highest-value decision in the whole exercise.
The methods that actually matter
Almost every integration is built from a handful of calls.
- `search_read` finds records and returns fields in one round trip. Prefer it over `search` followed by `read`, which is two.
- `create` and `write` accept lists, so writing a hundred records is one call rather than a hundred. This is the difference between an import that takes seconds and one that takes an hour.
- `fields_get` returns the schema of a model, which is how you discover what a field is actually called rather than guessing from the label shown in the interface.
- `search_count` answers "how many" without transferring the records.
Two habits separate integrations that scale from ones that do not. Always pass an explicit field list, because omitting it returns every field including large text and binary ones. And always batch, because per-record calls multiply network latency by row count and that is usually the entire performance problem.
Webhooks and the polling trap
Polling Odoo every minute to ask whether anything changed is the default design, and it is wasteful in both directions. It puts constant load on the database and it still leaves you up to a minute stale.
Odoo can call you instead. Automated actions can fire an outgoing request when a record is created or updated, which turns "did anything change" into a push. The pragmatic pattern is to send a thin webhook carrying the model, the record id and the trigger, then have your system call back for the detail it needs. That keeps the payload stable even when fields change, and it avoids putting business data in a request you may not control the transport of.
Receiving webhooks works the other way: an HTTP controller in your own module, with a shared secret or signature check, and a fast response. Do the real work asynchronously. A webhook handler that performs a long operation inline will eventually time out on the sender's side and be retried, and duplicate processing is the second most common integration bug after authentication.
Failure is normal, so design for it
Networks fail, servers restart, upgrades happen mid-sync. The integrations that survive treat failure as an expected state rather than an exception.
Make writes idempotent where you can, by keeping the external system's identifier on the Odoo record so a retry updates rather than duplicates. Retry with backoff rather than immediately, because an instance that is briefly overloaded is made worse by a client that hammers it. Log the request and the response, not just the exception, because "it failed" without the payload is unresolvable a week later. And decide explicitly what happens to a record that has failed ten times, because the default of retrying forever is how a queue quietly stops moving.
Test against a copy of real data, not demo data. The records that break integrations are the ones with unusual characters, missing optional fields and historical values nobody remembers creating, and no seeded database contains those.
Where this connects
An API is the mechanism. The reason you are using it is almost always an integration with a specific system, and the shape of that integration matters more than the endpoint you choose. If you are writing the Odoo side yourself, the development conventions apply to your controllers exactly as they do to any other module. And if you are upgrading, remember that the model schema is your API contract, which makes every integration a stakeholder in the migration plan.
Further reading
From the blog.
Longer pieces that go past what a single app guide covers.
FAQ
Odoo API, answered.
Is the Odoo API included in Community, or only Enterprise?
It is part of the framework and available in both editions. There is no API tier, no per-call pricing and no separate module to buy. In Odoo 19 the endpoints are provided by a small module called RPC that installs automatically.
Are XML-RPC and JSON-RPC really being removed?
The Odoo 19 source logs a deprecation warning on every call to /xmlrpc, /xmlrpc/2 and /jsonrpc, stating they are deprecated in 19 and scheduled for removal in Odoo 22. They still work today, but any new integration should target the newer JSON endpoint, and existing ones now have a known deadline.
How do I authenticate against the Odoo API?
Use an API key, not a password. Keys are created per user under My Profile and Account Security, can be revoked individually, and are what the newer endpoint expects in an Authorization Bearer header. A password embedded in an integration is a credential nobody can rotate without breaking something.
Does the API bypass access rights?
No, and this is the most important thing to understand about it. Calls execute as the authenticated user and are subject to exactly the same access rights and record rules as the interface. An integration that runs as an administrator has administrator reach, including delete.
What is the difference between the API and a webhook?
The API is you calling Odoo. A webhook is Odoo calling you when something happens, so you do not have to poll. Most real integrations need both: webhooks for freshness and an API call for the detail behind the event.
Are there rate limits on the Odoo API?
There is no published per-call quota the way a SaaS vendor would define one, but there are real limits: worker count, request timeouts and database load. The practical constraint is that a chatty integration making thousands of small calls will degrade the instance for its users long before it hits anything called a limit.
Can I build a proper REST API on top of Odoo?
Yes, with HTTP controllers, and it is the right move when an external consumer needs stable endpoints that do not expose your model names. The cost is that you now own versioning, authentication and documentation for those routes, which the built-in endpoints handle for you.
Related
Keep exploring.
Other Odoo apps we have written up in the same depth.
With CODEerts
Building against the Odoo API?
We are certified Odoo partners. We build and maintain Odoo integrations and custom endpoints, and we publish our own apps on the Odoo Store.
See how we can helpBook a callReady to make Odoo work the way your business does?
Book a free callCODEerts is a team of certified Odoo partners and full-stack engineers. We implement, customise and support Odoo ERP, then build the software around it.