For most production integrations, use JSON-RPC through execute_kw against a dedicated service user secured with a scoped API key. Reserve REST for simple, well-supported CRUD scenarios, and keep XML-RPC around only for legacy clients that predate JSON support. Skip polling wherever possible. Event-driven webhooks with signature verification and idempotency checks scale better and cost less than any scheduled pull.
TL;DR:
- Use JSON-RPC for full access to Odoo models and methods; verify REST coverage on the actual modules and fields before relying on it.
- Generate service-specific API keys tied to scoped user accounts and store them securely in secrets managers, rotating regularly.
- Employ webhooks with signature verification, idempotency, and asynchronous processing to achieve real-time updates and reduce server load.
- Limit polling by switching to event-driven webhooks, and enforce rate limiting and batching to prevent overload and improve performance.
- Build custom integrations for core modules with incomplete REST coverage, and consider middleware or low-code platforms for connecting multiple systems efficiently.
Table of Contents
- Comparing Odoo’s API surfaces: JSON-RPC, XML-RPC, and REST
- How do you secure Odoo API keys and service accounts?
- How do you call methods and perform CRUD in Odoo?
- Why webhooks beat polling for real-time integration
- What rate limits and performance patterns matter most?
- How should you handle errors, retries, and idempotency?
- Choosing an integration architecture: direct, middleware, or low-code
- Enterprise checklist for Odoo API integration delivery
- What actually breaks Odoo integrations in production
- How Singleclic delivers Odoo API integrations
- Key documentation and reference links
- Sources
- FAQ
Comparing Odoo’s API surfaces: JSON-RPC, XML-RPC, and REST
Odoo exposes three ways to talk to it from the outside, and picking the wrong one early costs weeks later. The official developer documentation covers all three, but the practical differences matter more than the reference pages let on.
XML-RPC is the oldest of the three. It wraps requests in XML payloads and works through libraries available in nearly every language, which is why it survives in so many legacy connectors. The Apache XML-RPC project still maintains reference client code that many older Odoo integrations depend on. The tradeoff is verbosity: XML payloads are heavier to parse and debug than JSON, and tooling support has largely moved on.
JSON-RPC (including the JSON-2 API introduced with newer Odoo releases) is the closest thing to a full-access door into Odoo’s ORM. Because Odoo methods are Python model calls, not native REST resources, JSON-RPC access through execute_kw maps directly onto that model structure, which is why it consistently reaches modules and methods that other surfaces miss.
REST in Odoo has improved, but coverage still lags for modules like HR, payroll, and accounting. Practitioners consistently recommend defaulting to JSON-RPC when an integration needs full method access and reliability rather than betting on REST parity that may not exist for the module you need.
Before committing to REST for any integration, validate coverage against your actual modules:
- Confirm the target model exposes the fields and methods you need through REST, not just through the ORM.
- Test create, read, update, and delete operations against a staging database, not assumptions from documentation.
- Check whether custom fields or third-party app models are reachable at all through REST.
- Budget time to fall back to JSON-RPC if REST gaps surface mid-project.
If REST coverage looks incomplete for your modules, a unified API layer or middleware wrapper is often a faster path than forcing REST to do work it wasn’t built for.
How do you secure Odoo API keys and service accounts?
Generate API keys inside Odoo under Settings → Users → API Keys, tied to a specific user account rather than an administrator login. This single decision determines how contained a breach stays if a key ever leaks.
Create a dedicated service user for each integration, scoped to only the models and actions that integration touches. An integration that only needs to read inventory levels should never carry a key with write access to accounting. This is basic least-privilege thinking, but it’s the step most teams skip under deadline pressure, and it’s the one that turns a minor leak into a serious incident.
Secrets storage deserves the same discipline. OWASP’s Secrets Management Cheat Sheet recommends storing API keys in a dedicated secrets manager rather than in environment files or version control, rotating them on a fixed schedule, and minimizing how many systems ever see the raw value. Apply the same rules to Odoo keys:
- Store keys in a secrets manager or vault, never in application code or committed config files.
- Rotate keys on a defined schedule, and immediately after any team member with access leaves.
- Log key usage so an anomalous spike in calls from one key gets flagged.
- Restrict each key’s service user to the minimum models and methods the integration actually needs.
Transport security is the other half of this. Every API call, whether XML-RPC, JSON-RPC, or REST, needs to run over HTTPS. Plaintext HTTP exposes both the key and the payload to interception, and TLS is a baseline requirement for any API traffic carrying business data.
Pro Tip: Create a separate API key per integration, even when two integrations share the same service user. If one key gets compromised, you revoke exactly one connection instead of taking down every system that depends on that user.
How do you call methods and perform CRUD in Odoo?
Every meaningful Odoo integration eventually comes down to one call pattern: execute_kw for XML-RPC and JSON-RPC clients, or /web/dataset/call_kw for JSON-RPC over HTTP. Both route through the same underlying ORM methods, since Odoo doesn’t distinguish between an internal button click and an external API call once it reaches the model layer.
- Create records by calling the
createmethod with a dictionary of field values. Odoo returns the new record’s ID, which you should store immediately for any follow-up operations. - Read records with
search_read, combining a domain filter with a list of fields. Requesting only the fields you need, instead of the full record, cuts payload size dramatically on wide models likesale.orderorres.partner. - Update records with
write, passing the record ID (or list of IDs) and a dictionary of changed fields. Batch updates across multiple IDs in a single call rather than looping one record at a time. - Delete records with
unlink, but check for dependent records first. Odoo will raise an integrity error rather than silently orphaning related data.
Domain filters are where most new integrators stumble. A domain like [['state', '=', 'sale'], ['amount_total', '>', 1000]] combines conditions with implicit AND logic. Mixing AND and OR requires prefix notation: ['&', ['state', '=', 'sale'], '|', ['amount_total', '>', 1000], ['amount_total', '<', 100]]. It reads awkwardly at first, but it’s consistent once you’ve written a handful.
Pagination matters the moment your record counts climb past a few hundred. Pass limit and offset parameters to search_read rather than pulling every record in one call. This keeps response times predictable and avoids timeout errors on large datasets.
Relational fields (many2one, one2many, many2many) return as IDs or lists of IDs, not full nested objects, so plan for a follow-up read if you need the related record’s data. Binary fields, including document attachments, come back base64-encoded, which inflates payload size fast. Fetch binary data in a separate, targeted call rather than including it in a bulk list read.
Why webhooks beat polling for real-time integration
Polling Odoo every few minutes to check for changes wastes server cycles and introduces lag that no business process actually tolerates well. Event-driven webhooks reduce both load and latency compared with polling, because Odoo pushes data the instant something changes instead of waiting to be asked.
Configure webhooks and automated actions under Settings → Technical → Automation → Automated Actions, where you can trigger outbound calls on record create, write, or delete events. Use automated actions for straightforward “notify an external system” cases, and reach for a custom module when the trigger logic gets complex enough that the UI-based rule builder becomes hard to maintain.
Building a secure receiver on the other end matters just as much as configuring the sender:
- Verify a signature on every incoming payload so you can confirm it actually came from your Odoo instance.
- Check timestamps to reject stale or replayed requests.
- Return a quick 200 acknowledgment immediately, then hand heavy processing to a background worker or queue.
- Design every webhook handler to be idempotent, since networks retry, and duplicate deliveries will happen.
The Braincuber webhooks tutorial makes a point worth repeating: acknowledge first, process second. A receiver that does heavy database writes before responding will eventually time out under load, and Odoo will interpret that timeout as a failure and retry, compounding the problem instead of resolving it.
Pro Tip: Route incoming webhook payloads into a lightweight message queue (RabbitMQ, Redis Streams, or a cloud equivalent) before processing. This decouples “receiving the event” from “acting on the event,” so a slow downstream system never blocks Odoo’s webhook delivery.
What rate limits and performance patterns matter most?
Every polling cycle you run against Odoo is a query load you’re choosing to carry even when nothing changed. Switching a high-frequency polling job to an event-driven webhook typically eliminates the majority of those wasted calls, since the integration only fires when there’s actually something to process.
Rate limiting protects both sides of the connection. Enforce sensible throttling at the infrastructure level using something like Nginx or an API gateway in front of Odoo, rather than relying on application code alone to self-police. A reasonable approach is to cap bursts per service account and apply backoff signals to handle rate limiting rather than silently dropping requests.
Batching cuts call volume dramatically. Instead of creating a hundred records with a hundred separate create calls, pass a list of value dictionaries in one call where the method supports it. The same applies to reads: one search_read with a broad domain beats a hundred targeted lookups.
Monitor these metrics on any integration running in production:
- Average and peak response time per endpoint.
- Error rate by error type (timeout, permission, validation).
- Call volume per service account, to catch runaway loops early.
- Queue depth, if you’re running webhook processing asynchronously.
A well-architected integration replaces continuous polling with event triggers and batched calls, which is usually the single biggest lever available for reducing load on a shared Odoo instance.
How should you handle errors, retries, and idempotency?
Not every failure deserves the same response. Classify errors before deciding how to react:
- Transient errors (network timeouts, temporary 503s) should trigger exponential backoff retries, spacing each retry attempt progressively further apart rather than hammering the endpoint immediately.
- Validation errors (bad field values, missing required data) should fail fast and log the payload for review. Retrying a request that was wrong the first time just repeats the failure.
- Permission errors point to a misconfigured service user or an expired key, and should alert a human rather than retry silently.
Idempotency keys prevent the most common production incident in integrations: duplicate records from a retried request. Attach a unique key to each create operation and check for that key’s existence before processing, particularly on financial or inventory records where safe write patterns and transaction isolation genuinely matter.
Test integrations against a staging database, never production, using contract tests that verify field names and types haven’t shifted across an Odoo upgrade. Maintain a Postman collection (or equivalent) covering every endpoint you depend on, and wire it into CI so a broken integration surfaces in a pull request instead of in a client’s live environment.
Pro Tip: Store idempotency keys with a short expiration window, not forever. A key that never expires eventually becomes a storage problem; one that expires too fast risks accepting a genuine duplicate.
Choosing an integration architecture: direct, middleware, or low-code
Three architecture patterns cover almost every Odoo integration project, and picking the wrong one is usually a scoping mistake made in week one.
A direct connector, calling Odoo’s API straight from your application code, works well for a single, well-defined integration with stable requirements. It’s fast to build and has no extra infrastructure, but it becomes a maintenance burden the moment you need five or six of these connectors across different systems.
Middleware or an iPaaS layer makes sense when REST coverage gaps push you toward a unified API wrapper, or when you’re connecting Odoo to several external systems at once and need centralized error handling, logging, and retry logic in one place rather than duplicated across connectors.
Low-code orchestration fits enterprise environments where approvals, legacy systems, and multiple ERPs or CRMs all need to talk to each other, and where the integration logic itself changes often enough that hardcoded connectors become a bottleneck. This is where a platform like Cortex connects Odoo, Dynamics, and legacy systems without a custom codebase for every new connection, letting workflow changes happen without redeploying integration code.
Deployment context shifts the calculus too. On-premise Odoo deployments, common among banks and government entities with data residency requirements, need a network architecture that accounts for firewall rules and VPN access before any API traffic flows. Cloud-hosted Odoo simplifies connectivity but shifts the security conversation toward access control and key management instead.
- Direct connector: fastest to build, hardest to scale across multiple integrations.
- Middleware/iPaaS: best when REST coverage is incomplete or multiple systems need centralized handling.
- Low-code orchestration: best for enterprise environments with evolving workflows and multiple connected systems.
Odoo’s modular ERP and CRM architecture tends to reward whichever pattern matches your actual integration count, not the one that looked simplest on a whiteboard.
Enterprise checklist for Odoo API integration delivery
Singleclic delivers Odoo integrations as an Odoo Silver Partner with regional teams across KSA, UAE, and Egypt, working across sectors including real estate, construction, and healthcare. A typical engagement runs through discovery and API coverage validation, service user and key provisioning, build and staging tests, then a phased go-live with monitoring in place before full cutover.
Before any integration goes live, confirm these items:
- Every service account follows least-privilege access, scoped to only what that integration touches.
- Webhook receivers verify signatures and respond within timeout windows.
- Idempotency keys are implemented on every write operation.
- Staging tests cover the actual Odoo version in production, not just the latest release.
- Monitoring is live for error rates and call volume before cutover, not added afterward.
Enterprise deployments, particularly integrations touching regulated data, need this checklist enforced before go-live, not discovered during an incident.
What actually breaks Odoo integrations in production
The most common mistake isn’t a coding error. It’s assuming REST coverage exists for a module before checking, then discovering three weeks into a build that the accounting or HR endpoint the project depends on simply isn’t there. The second most common mistake is polling on a tight interval “to be safe,” which quietly degrades Odoo’s performance for every other user on the instance long before anyone traces the cause.
Build custom when the integration is core to the business and needs precise control over data mapping. Buy or integrate through middleware when you’re short on time, REST coverage is incomplete, or you’re connecting more than two or three systems. Most teams wait too long to make that second call.
— Tamer Badr
How Singleclic delivers Odoo API integrations
There are legitimate paths here: build the connector yourself, hire a freelancer, or lean on a generic API wrapper. Each works until the integration touches a module REST doesn’t fully cover, or until a second and third system need to join the same workflow, and suddenly you’re maintaining custom code with no orchestration layer behind it.

Singleclic builds Odoo integrations as an Odoo Silver Partner, and where the job calls for connecting Odoo to multiple systems or complex approval chains, a low-code platform can orchestrate those connections without stacking one-off connectors on top of each other. A low-code platform may run on-premise with Arabic UI support, addressing needs of banks and government entities in KSA and the UAE that require on-premise integration logic rather than cloud deployment. Regional teams have experience handling projects of this nature for clients in healthcare and banking sectors. If your integration needs to connect Odoo with a broader Microsoft Dynamics 365 environment, reach out for a scoping call and we’ll map your API coverage gaps before you write a line of connector code.
Key documentation and reference links
- External API — Odoo 18.0 documentation
- External JSON-2 API — Odoo 19.0 documentation
- Secrets Management Cheat Sheet — OWASP
- How to integrate with the Odoo API — Apideck blog
Sources
FAQ
How can I integrate with the API in Odoo?
Generate an API key under Settings → Users → API Keys, connect using XML-RPC or JSON-RPC with that key, then call methods like search_read, create, write, and unlink through execute_kw against the model you need.
Is the Odoo API free to use?
Access to Odoo’s external API is included with any Odoo instance, community or enterprise, at no separate cost. What you pay for is the underlying Odoo license or hosting, not the API itself.
How do I integrate two systems using an API?
Identify the authentication method both systems support, map the data fields between them, build create/read/update calls for the core objects, then add error handling and testing before connecting production data.
What are the typical stages of an API integration project?
Most projects move through discovery and coverage validation, credential and access setup, build and staging tests, a phased go-live, and ongoing monitoring, roughly matching the delivery phases Singleclic follows on Odoo integration engagements.
Should I use REST or JSON-RPC for Odoo integrations?
Use JSON-RPC for most production integrations, since it reaches Odoo’s full model and method set. Use REST only after confirming your specific modules and fields are actually covered.
Recommended
- ERP + CRM Integration: Build a Single Source of Truth with Dynamics 365 and Odoo
- ERP Integration with Low-Code: How Cortex Connects Odoo, Dynamics, and Legacy Systems
- Odoo ERP, Accelerated: Singleclic’s Blueprint for Integrated, Data‑Driven Operations
- QuickBooks Online at Scale: Integrate, Automate, or Migrate with Dynamics 365 and Odoo







