---
title: "Odoo API Integration Limits: Rate Cap, JSON-2, Upgrades"
canonical: https://wavect.io/blog/odoo-erp-api-integration-limits-2026/
language: en
description: "Odoo allows about one API call per second, runs every JSON-2 call in its own transaction, and removes XML-RPC by 2028. How to design an Odoo integration around that."
image: "https://wavect.io/img/blog/headers/header_odoo-erp-api-integration-limits-2026.png"
---

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

12 min read · 18 Aug 2026 Last reviewed August 18, 2026

[**Next**](/blog/fintech-architecture-remediation-without-rewrite/)

# Odoo Gives Your Integration One API Call per Second. Everything Else Follows From That.

TL;DR

Five documented constraints decide an Odoo integration before anyone writes code. Odoo's JSON-2 reference states that external API access exists only on Custom pricing plans and not on One App Free or Standard. Odoo's acceptable use policy calls roughly one call per second, unsustained and with no parallel calls, acceptable on its cloud, and names dedicated Odoo.sh hosting as the way to lift that. Every call to the JSON-2 endpoint runs in its own SQL transaction and cannot be chained, which Odoo itself calls especially dangerous for reservations and payments, so any all-or-nothing operation belongs in a single method inside a custom module. The XML-RPC and JSON-RPC endpoints are scheduled for removal in Odoo 22 in fall 2028 and in Odoo Online 21.1 in winter 2027. And a database with custom modules cannot upgrade until those modules are compatible, while Odoo Online upgrades run on Odoo's schedule, with field renames such as res.users.groups_id becoming group_ids in 19.0 breaking clients that hardcode names. The shape that survives is one boundary service you own: an anti-corruption layer, your own read store fed by webhooks, a single serialized writer with idempotency keys, transaction-scoped custom methods for invariants, and contract tests against an upgraded copy of the database.

Search for Odoo integration advice and you get two kinds of page: a partner brochure, or a tutorial that shows you how to call `search_read` from Python. Neither one tells you the thing that actually decides your architecture. Odoo publishes hard limits on what an external integration is allowed to do, and two of those limits have dates attached to them.

This is written from the build side, for the team that has to connect a product, a customer portal, a shop, a pricing engine or an internal tool to an Odoo database and keep it working through Odoo's release train. It assumes Odoo stays. Nothing here is an argument for replacing an ERP that already runs your accounting. The only question worth answering is where the boundary sits between Odoo and the software you own, and Odoo's own documentation answers most of it if you read the right five pages.

**Answer first:** five constraints decide an Odoo integration before anyone writes code. The external API is a paid-plan feature, not a platform feature. Odoo Cloud treats roughly one call per second with no parallel calls as acceptable use. Every JSON-2 call runs in its own transaction, so a multi-step business operation needs one server-side method, not four client calls. The old `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` endpoints have a removal date. And a custom module blocks your upgrade while Odoo Online upgrades run on Odoo's schedule. Build the integration as one service you own, with its own store, idempotent writes and an event-driven inbound path, and four of the five stop being emergencies.

## The five limits, in one table

| Limit | What Odoo states | What it forces on your design |
| --- | --- | --- |
| Plan gate | External API access is available on Custom pricing plans, and not on One App Free or Standard | The API is a line item in the ERP contract, not a free capability. Confirm it before you scope the integration |
| Call budget | Roughly one call per second, unsustained, with no parallel calls, is what the acceptable use policy calls acceptable on Odoo Cloud | Batch reads, aggregate server-side, and let Odoo push events to you instead of polling it |
| Transaction boundary | Every call to the JSON-2 endpoint runs in its own SQL transaction, and calls cannot be chained inside one transaction | Any multi-step operation that must be all-or-nothing belongs in a single method inside a custom module |
| Protocol clock | `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` are scheduled for removal in Odoo 22 (fall 2028) and Odoo Online 21.1 (winter 2027) | New work targets `/json/2`. Existing RPC clients need a dated migration plan, not a backlog ticket |
| Upgrade clock | Each major version is supported for three years, Odoo Online upgrades are mandatory on a schedule, and a database with custom modules cannot upgrade until those modules are compatible | Custom code is a recurring maintenance liability with someone's name on it, and your integration tests are part of the upgrade gate |

Read the last two rows together, because they interact in the way that hurts. Your RPC client has to be gone by the time Odoo Online reaches 21.1, and the upgrade that takes you there is an upgrade you do not fully control.

## Limit 1: the external API is a plan feature

The first surprise is commercial, not technical. Odoo's [JSON-2 API reference](https://www.odoo.com/documentation/19.0/developer/reference/external_api.html) states that access to data via the external API is only available on Custom pricing plans, and that it is not available on One App Free or Standard plans.

That single note reorders a lot of projects. A company that picked the Standard plan because it looked like the sensible middle option has bought an ERP that its own software cannot talk to. The integration budget then has to absorb a plan change for every user, every month, forever. It is the cheapest thing to verify and the most expensive thing to discover in week six.

Two practical notes. First, self-hosted Odoo Community has no plan gate at all, because there is no plan, which is one of the few real arguments for running it yourself. Second, plan names and tiers are marketing surface and they move, so treat the documentation note as the reason to check your own contract rather than as a quote to paste into a decision memo.

## Limit 2: one call per second, and no parallel calls

Odoo does not publish a per-endpoint quota with a documented 429 threshold. What it publishes is a policy. The [acceptable use policy for Odoo Cloud](https://www.odoo.com/acceptable-use) lists unthrottled RPC and API calls as prohibited network abuse, notes that batch APIs exist for imports, and says that throttled calls are typically acceptable for unsustained usage at a rate of one call per second, with no parallel calls. It also names the escape hatch: on Odoo.sh, dedicated hosting can be considered as an alternative to lift that restriction.

A policy ceiling is harder to design against than a documented quota, because you cannot measure your way to safety. You have to assume the number is the number. One call per second, serial, is a budget of about 86,000 calls a day if you use every second, which no real integration does. So the design moves from "how fast can we go" to "how few calls does this need".

### What that buys you, in call terms

- **One round trip instead of two.** `search_read` replaces a `search` followed by a `read`. Every combined ORM method is a call you did not spend.
- **Aggregate inside Odoo.** `read_group` returns grouped sums from the database. Pulling 40,000 order lines to total them in your own code is the same answer at a hundred times the cost.
- **Write in batches.** Passing a list of ids to one write is one call. A loop over ids is one call per record, and a loop of 3,000 records is an outage you caused.
- **Ask for the fields you need.** A `fields` list keeps the response small and stops your client from depending on columns you never wanted, which matters again at upgrade time.
- **Never poll for change detection.** Polling is the single largest consumer of a one-per-second budget, and it is the one that scales with your customer count rather than your data volume.

The replacement for polling is already in the product. Odoo's [automation rules](https://www.odoo.com/documentation/19.0/applications/studio/automated_actions.html) include a Send Webhook Notification action that posts the selected fields of a record to a URL you specify, with a sample payload preview, and they can also be triggered by an inbound webhook from an external system. Odoo's own documentation attaches a warning to this: get a developer or solution architect involved, because a badly configured webhook can disrupt the database. That warning is correct and it is the reason webhooks belong behind a queue on your side, not wired straight into business logic.

The shape that survives is boring. Odoo pushes an event. Your service accepts it, writes it to your own store, returns 200 fast, and does the real work asynchronously with a retry policy and a rate limiter that respects one call per second. Your product reads from your store, not from Odoo. Odoo stays the system of record for the things it owns.

## Limit 3: every call is its own transaction

This is the limit that produces the expensive class of bug, and it is stated plainly in the [transaction section of the JSON-2 reference](https://www.odoo.com/documentation/19.0/developer/reference/external_api.html#transaction): all calls to the JSON-2 endpoint run in their own SQL transaction, committed on success and discarded on error, and it is not possible to chain multiple calls inside a single transaction. The documentation goes further and says the database might be modified by other concurrent transactions between your calls, and that this is especially dangerous for operations related to reservations and payments.

Read that as an architectural instruction, because it is one. Odoo's recommended fix is in the same paragraph: always call a single method that performs all the related operations in a single transaction, and where no such method exists, create one in a dedicated module.

Which means the honest answer to "can we do this without touching Odoo code" is often no. A three-call sequence that checks stock, reserves it, and confirms an order is not a transaction. It is three transactions with two windows in which another user, another integration or a scheduled action can change the world underneath you. The failure is not a crash. It is a double reservation, a payment against a stale price, or an order line that exists without its parent, discovered by accounting a month later.

### Three rules we apply to every Odoo write path

1. **One business operation, one call.** If the operation has an invariant, it gets a method in a custom module and your service calls that one method. The invariant lives in the database transaction, not in your orchestration code.
2. **Idempotency keys on everything that creates.** Your retry policy will re-send a call whose response you never saw. Store your own key on the Odoo record, check it before creating, and a retry becomes a no-op instead of a duplicate invoice.
3. **Reconcile, do not trust.** A nightly job that compares your store against Odoo on a small number of aggregate reads catches the drift that per-call error handling misses. Cheap in calls, and it is the only thing that finds the failures nobody logged.

## Limit 4: the RPC protocol has a removal date

Almost every existing Odoo integration in the wild speaks XML-RPC, because for many years that was the answer. The [External RPC API reference](https://www.odoo.com/documentation/19.0/developer/reference/external_rpc_api.html) now opens with a removal notice: both the XML-RPC and JSON-RPC APIs at `/xmlrpc`, `/xmlrpc/2` and `/jsonrpc` are scheduled for removal in Odoo 22 (fall 2028) and Odoo Online 21.1 (winter 2027), with the External JSON-2 API as the replacement. All three exposed services, common, db and object, are deprecated. Internal controllers declared with `@route(type='jsonrpc')` are explicitly not covered by that notice.

Two dates, and the one that binds you depends on where your database lives. Odoo Online reaches the cutoff first, in winter 2027, because Odoo Online ships minor versions and moves faster than the major release line. Self-hosted and Odoo.sh databases have until Odoo 22 in fall 2028, plus however long you are prepared to sit on an unsupported version.

The migration itself is small if your call layer is one module and painful if RPC calls are scattered through your codebase. What changes:

| Concern | XML-RPC and JSON-RPC | External JSON-2 |
| --- | --- | --- |
| Endpoint | `/xmlrpc/2/common` then `/xmlrpc/2/object` | `POST /json/2/<model>/<method>` |
| Authentication | Log in first, carry the returned user id into every subsequent call | An API key as a bearer token in the `Authorization` header, no login round trip |
| Database selection | A positional argument in every call | The optional `X-Odoo-Database` header, needed when one server hosts several databases |
| Payload | Positional arguments in a fixed order | A JSON object with `ids`, `context` and named parameters |
| Discovery | Read the source of the model you are calling | A per-database documentation route at `/doc`, generated from that database's own models |

The authentication change is the one worth planning around rather than porting mechanically. JSON-2 keys can be created, revoked and rotated programmatically through `res.users.apikeys`, with an expiration date that is validated against the maximum key duration allowed by the user's roles. Odoo's guidance is to run automated integrations under a dedicated bot user with the minimum permissions and an empty password, so the login route is closed and the access log names the bot rather than impersonating a person. Do that once, per integration, and key rotation becomes an ops task instead of a migration.

## Limit 5: the upgrade clock belongs to Odoo

The [upgrade documentation](https://www.odoo.com/documentation/19.0/administration/upgrade.html) is the page most integration plans skip, and it contains the constraint with the longest tail. Each major version is supported for three years. On Odoo Online, an upgrade is mandatory every two years for a major version, and a few weeks after the next release for a minor version, with minor versions arriving roughly every two months. Odoo's Upgrade Team runs a silent test upgrade of every database that is due, and if that test succeeds and takes under twenty minutes, the automatic upgrade proceeds unless you act before the deadline.

Then the two sentences that decide who carries the cost. A database that contains custom modules cannot be upgraded until a version of those modules exists for the target Odoo version. And if a change in a new version breaks a customization, making it compatible is the responsibility of the maintainer of that custom module. Odoo's own testing checklist puts integrations with external software, APIs and EDI first among the things you must test.

That is not a complaint about Odoo. It is the deal, and it is a reasonable deal. But it means every custom module and every integration you build has a recurring cost with a name attached, and the name has to be a real person or a real contract. Version 19 makes the point concrete: `res.users.groups_id` [became `group_ids` in the 19.0 source](https://github.com/odoo/odoo/blob/19.0/odoo/addons/base/models/res_users.py), and the separate contract model moved into HR core as [`hr.version`](https://github.com/odoo/odoo/blob/19.0/addons/hr/models/hr_version.py). Any external client that named those fields returns a clean 200 with the wrong shape, or an error, depending on the call. Neither is caught by a test suite that mocks Odoo.

The cheap defence is a contract test: a small suite that runs your real read and write paths against an upgraded copy of the database and asserts on the fields you actually depend on. It is the same discipline as any other [API](/glossary/api/) you do not control, and it turns the mandatory upgrade window from a fire drill into a morning.

## Where hosting decides the argument

Three of the five limits are set by where the database runs, which makes hosting an architectural decision rather than a procurement one.

| Option | Call budget and API access | Upgrade control | Fits when |
| --- | --- | --- | --- |
| Odoo Online | Acceptable use ceiling applies, external API tied to the plan tier | Rolling Release, mandatory upgrades on Odoo's schedule, RPC endpoints gone by 21.1 in winter 2027 | Standard processes, light integration, no appetite for infrastructure |
| Odoo.sh | Same policy, with dedicated hosting available as the documented way to lift the throttling restriction | Staged branches and test builds, upgrades on your trigger within the support window | Real custom modules, a CI habit, integration traffic that a shared ceiling would throttle |
| Self-hosted Community | No plan gate and no policy ceiling, you own the capacity and the reverse proxy | Entirely yours, including the decision to fall out of support | Heavy integration, data residency requirements, in-house ops that already exists |

One licensing fact matters here and is regularly guessed at. The Odoo Community source ships under [the LGPL version 3](https://github.com/odoo/odoo/blob/19.0/LICENSE), which is why a proprietary custom module on top of Community is a normal thing to do rather than a legal argument. Enterprise editions and Odoo's own hosted plans carry separate commercial terms, so read those against your own case instead of inheriting an opinion from a forum thread.

Self-hosting is not free. You are buying the removal of a rate ceiling with the addition of backups, upgrades, monitoring, a reverse proxy you now rate-limit yourself, and someone on call. That trade is the same one described in our guide to [custom software versus off-the-shelf](/software-development-guide/custom-software-vs-off-the-shelf/): pay for the thing your business logic actually touches, configure the rest.

## The reference shape we build

Every Odoo integration we have looked at converges on the same architecture once the five limits are on the table. It is one service, owned by your team, sitting between Odoo and everything else.

1. **An anti-corruption layer.** One module in your codebase knows Odoo's model names, field names and quirks. Nothing else in your product imports it. When `groups_id` becomes `group_ids`, you change one file.
2. **Your own read store.** Whatever your product reads on the hot path lives in your database, populated by events and reconciled on a schedule. Product latency stops depending on an ERP under a call ceiling.
3. **An inbound webhook endpoint with a queue.** Accept, persist, return 200, process asynchronously, retry with backoff. Odoo's automation rule is the producer and your queue absorbs bursts that a synchronous handler would drop.
4. **An outbound writer with one lane.** A single serialized worker enforces the call budget in one place, carries idempotency keys, and gives you one dashboard for the whole integration's call volume.
5. **Custom module methods for invariants.** Small, named, transaction-scoped methods for the operations that must not tear. Reviewed and versioned like product code, because at upgrade time they are your liability.
6. **Contract tests against a real upgraded database.** The gate that makes Odoo's upgrade schedule survivable.

The closest analogue in our own work is not an ERP at all. On the [MyMerch process automation engagement](/case-studies/mymerch/) the client ran a working commerce operation across many storefronts and was losing margin in two specific places. The tempting proposal was a rewrite. What actually worked was naming the manual handoffs, automating those, and leaving the platform alone. The same instinct applies to an ERP: the integration boundary is where the value is, and the platform underneath it is usually fine. We wrote up the general version of that argument in [architecture remediation without a rewrite](/blog/fintech-architecture-remediation-without-rewrite/).

## Four things we would not do

- **Do not mirror Odoo in real time.** A two-way sync of shared mutable state across a one-per-second link is a distributed systems project wearing an integration costume. Pick a writer per field and keep it.
- **Do not move core accounting into your product.** The moment invoices, tax logic or the chart of accounts live in two systems, you own reconciliation forever. If a statutory obligation is pushing you there, the answer is usually a layer beside the ERP rather than inside your app, which is the same conclusion we reached about [structured e-invoicing around a payment processor](/blog/stripe-billing-e-invoicing-2027/).
- **Do not put business logic in automation rules.** Server actions and code blocks in the UI are invisible to code review, untested, and unversioned. They are excellent glue and a terrible place for rules that money depends on.
- **Do not let the integration be nobody's module.** Undocumented, unowned Odoo customizations are the most common reason an upgrade stalls, and the most common thing a new provider inherits blind. Our checklist for that handover situation is in [taking over a software project from another provider](/blog/software-project-takeover-provider-change/).

## A 30-day plan to get from unknown to safe

1. **Days 1 to 3, establish the facts.** Hosting type, exact version, plan tier and whether the external API is contractually available. Then list every existing integration and which protocol it speaks. Most teams cannot answer the last part on day one.
2. **Days 4 to 10, measure the call volume.** Log every outbound call for a week, grouped by caller and purpose. The polling jobs will be the top of that list and the first thing to delete.
3. **Days 11 to 18, name the invariants.** Write down every operation that must be all-or-nothing. Each one becomes a single custom module method with a test, or an accepted risk with a reconciliation job behind it.
4. **Days 19 to 25, build the boundary.** One anti-corruption module, one serialized writer with idempotency keys, one webhook endpoint with a queue. No new features in this window.
5. **Days 26 to 30, plan the JSON-2 cutover.** A dated plan tied to your own upgrade window, not to 2027. A dedicated bot user, per-integration API keys with expiry, and a rotation runbook.

Thirty days assumes someone owns the decisions. Where that owner does not exist, the work stalls at step two, which is a common reason companies bring in a [fractional CTO](/services/fractional-cto/) for a bounded integration programme instead of hiring for it.

## Odoo integration FAQ

### Is XML-RPC deprecated in Odoo?

Yes. Odoo's External RPC API reference states that the XML-RPC and JSON-RPC APIs at the /xmlrpc, /xmlrpc/2 and /jsonrpc endpoints are scheduled for removal in Odoo 22 in fall 2028 and in Odoo Online 21.1 in winter 2027, with the External JSON-2 API as the replacement. The common, db and object services are all deprecated. Internal controllers declared with route type jsonrpc are explicitly outside that notice.

### What is the Odoo API rate limit?

There is no published per-endpoint quota. Odoo's acceptable use policy for its cloud lists unthrottled RPC and API calls as prohibited abuse and says throttled calls are typically acceptable for unsustained usage at a rate of one call per second with no parallel calls. On Odoo.sh, dedicated hosting is named as the way to lift that restriction. Self-hosted databases are bounded by your own infrastructure instead.

### Do I need a paid Odoo plan to use the external API?

On Odoo's hosted plans, yes. The JSON-2 API reference states that access to data via the external API is only available on Custom pricing plans and is not available on One App Free or Standard plans. Self-hosted Community has no plan gate. Verify this against your own contract before you scope an integration, because a plan change prices per user per month.

### Can I run several Odoo API calls in one transaction?

No. Odoo documents that every call to the JSON-2 endpoint runs in its own SQL transaction and that chaining multiple calls inside one transaction is not possible, so concurrent transactions can change the database between your calls. Odoo's own recommendation is to call a single method that performs all the related operations, and to create such a method in a dedicated module when none exists.

### Should I use webhooks or polling with Odoo?

Webhooks, in nearly every case. Polling is the largest consumer of a one-call-per-second budget and it scales with your customer count rather than your data volume. Odoo's automation rules include a Send Webhook Notification action that posts selected record fields to your URL. Put a queue behind your endpoint, return 200 quickly, and process asynchronously, because Odoo's own documentation warns that a badly configured webhook can disrupt the database.

### Do custom modules block an Odoo upgrade?

They block it until a compatible version of the module exists for the target Odoo version, which Odoo states in its upgrade documentation. Odoo also states that when a new version breaks a customization, making it compatible is the responsibility of that module's maintainer. Odoo's testing checklist puts integrations with external software and APIs first among the things to re-test.

### How often must an Odoo Online database be upgraded?

Each major version is supported for three years. On Odoo Online an upgrade is mandatory every two years for a major version, and a few weeks after the next release for a minor version, with minor versions arriving roughly every two months. Odoo runs a silent test upgrade first, and if it succeeds and takes under twenty minutes the automatic upgrade proceeds unless you act before the deadline.

### What broke for integrations in Odoo 19?

Field and model renames are the ones that reach external clients. In the 19.0 source, res.users.groups_id is group_ids, and the separate contract model moved into HR core as hr.version. A client that hardcodes old names fails at the field level rather than at the connection level, so a contract test against an upgraded copy of your own database is the only reliable detector.

### Can I keep a proprietary custom module on Odoo Community?

The Odoo Community source ships under the LGPL version 3, which is why a proprietary module on top of Community is common practice rather than a legal argument. Enterprise editions and Odoo's hosted plans carry separate commercial terms, so check those against your specific case rather than generalising from Community.

## Final thoughts

Odoo is a good ERP with a deliberately narrow external contract, and it documents that contract honestly. The teams that struggle are the ones that treat it as an open database with a REST API bolted on. Read the five limits as design inputs: budget your calls, put invariants in one transaction, move to JSON-2 on your own schedule rather than in 2027, and give every custom module an owner who will still be there at the next upgrade. Build one boundary service you control, and the ERP becomes a dependency you manage instead of a deadline you react to.

## Primary sources

- [Odoo 19 documentation, External JSON-2 API, including the plan restriction, API key management and the transaction section](https://www.odoo.com/documentation/19.0/developer/reference/external_api.html)
- [Odoo 19 documentation, External RPC API, with the removal notice for the XML-RPC and JSON-RPC endpoints](https://www.odoo.com/documentation/19.0/developer/reference/external_rpc_api.html)
- [Odoo acceptable use policy, network and services abuse, on throttled call rates](https://www.odoo.com/acceptable-use)
- [Odoo 19 documentation, upgrade, on support duration, mandatory upgrades, Rolling Release and custom module responsibility](https://www.odoo.com/documentation/19.0/administration/upgrade.html)
- [Odoo 19 documentation, automation rules, including the Send Webhook Notification action](https://www.odoo.com/documentation/19.0/applications/studio/automated_actions.html)
- [Odoo 19.0 source, res.users, showing the group_ids field](https://github.com/odoo/odoo/blob/19.0/odoo/addons/base/models/res_users.py)
- [Odoo 19.0 source, the hr.version model in HR core](https://github.com/odoo/odoo/blob/19.0/addons/hr/models/hr_version.py)
- [Odoo 19.0 LICENSE file, LGPL version 3 for the Community source](https://github.com/odoo/odoo/blob/19.0/LICENSE)

## You may also like..

[**Architecture remediation without a rewrite** How to fix a system that carries real transactions without stopping the business to rebuild it.](/blog/fintech-architecture-remediation-without-rewrite/) [**Custom software vs off-the-shelf** The build, buy or configure decision applied to the layer around a standard platform.](/software-development-guide/custom-software-vs-off-the-shelf/)

Architecture and platforms

## Continue through this cluster

Framework, platform and system-design choices that affect delivery over the long term.

[Start with the cornerstone**Smart City Architecture Best Practices: MQTT, LoRaWAN, Kubernetes and Terraform**](/blog/smart-city-architecture-best-practices-2026/)

- [AI in Grocery Retail: An MPREIS Opportunity Map](/blog/mpreis-ai-grocery-retail-opportunity-map/)
- [Stateful LLM Platform Production Architecture](/blog/stateful-llm-platform-production-architecture/)
- [Tirol Kliniken (TILAK): A Digital Operations Opportunity Map](/blog/tirol-kliniken-digital-opportunity-analysis/)
- [TIWAG Digital Energy: A Software and AI Opportunity Map](/blog/tiwag-digital-energy-services-opportunity-map/)
- [AI 3D Model Generators for Game Development: 2026 Guide](/blog/ai-3d-model-generators-game-development-2026/)

Inbox, without the noise

## Follow the work that matters to you

Get a short email when we publish something new. Follow the whole blog or only the problems you care about.

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

12 min read · 18 Aug 2026 Last reviewed August 18, 2026

[**Next**](/blog/fintech-architecture-remediation-without-rewrite/)

New posts by email ×

×

Get new posts by email

A short email when we publish. Free, no tracking.

## Structured Data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://wavect.io/#organization",
      "@type": [
        "Organization",
        "ProfessionalService",
        "LocalBusiness"
      ],
      "employee": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "founder": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "legalRepresentative": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "name": "Wavect GmbH",
      "subjectOf": {
        "@id": "https://wavect.io/verified-claims.json#dataset",
        "@type": "Dataset",
        "creator": {
          "@id": "https://wavect.io/#organization",
          "@type": [
            "Organization",
            "ProfessionalService",
            "LocalBusiness"
          ]
        },
        "description": "A machine-readable registry of quantitative and qualitative claims published by Wavect, with review dates, localized page appearances and public third-party citations where available.",
        "inLanguage": "en",
        "isAccessibleForFree": true,
        "license": "https://creativecommons.org/licenses/by/4.0/",
        "name": "Wavect verified publication claims",
        "url": "https://wavect.io/verified-claims.json"
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/team/kevin-riedl/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Kevin Riedl",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796365",
        "https://www.linkedin.com/in/wsdt",
        "https://github.com/wsdt"
      ],
      "url": "https://wavect.io/team/kevin-riedl/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/team/christof-jori/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Christof Jori",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796367",
        "https://www.linkedin.com/in/jocr77/",
        "https://github.com/jo-chris"
      ],
      "url": "https://wavect.io/team/christof-jori/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/#website",
      "@type": "WebSite",
      "inLanguage": [
        "en",
        "de",
        "es",
        "zh"
      ],
      "name": "Wavect",
      "potentialAction": {
        "@type": "SearchAction",
        "query-input": "required name=search_term_string",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://wavect.io/search/?q={search_term_string}"
        }
      },
      "publisher": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/blog/odoo-erp-api-integration-limits-2026/#webpage",
      "@type": "WebPage",
      "dateModified": "2026-08-18",
      "inLanguage": "en",
      "isPartOf": {
        "@id": "https://wavect.io/#website",
        "@type": "WebSite"
      },
      "lastReviewed": "2026-08-18",
      "url": "https://wavect.io/blog/odoo-erp-api-integration-limits-2026/"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "abstract": "Five documented constraints decide an Odoo integration before anyone writes code. Odoo's JSON-2 reference states that external API access exists only on Custom pricing plans and not on One App Free or Standard. Odoo's acceptable use policy calls roughly one call per second, unsustained and with no parallel calls, acceptable on its cloud, and names dedicated Odoo.sh hosting as the way to lift that. Every call to the JSON-2 endpoint runs in its own SQL transaction and cannot be chained, which Odoo itself calls especially dangerous for reservations and payments, so any all-or-nothing operation belongs in a single method inside a custom module. The XML-RPC and JSON-RPC endpoints are scheduled for removal in Odoo 22 in fall 2028 and in Odoo Online 21.1 in winter 2027. And a database with custom modules cannot upgrade until those modules are compatible, while Odoo Online upgrades run on Odoo's schedule, with field renames such as res.users.groups_id becoming group_ids in 19.0 breaking clients that hardcode names. The shape that survives is one boundary service you own: an anti-corruption layer, your own read store fed by webhooks, a single serialized writer with idempotency keys, transaction-scoped custom methods for invariants, and contract tests against an upgraded copy of the database.",
  "articleBody": " Blog overview/Delivery and QA/Architecture and platforms Odoo Gives Your Integration One API Call per Second. Everything Else Follows From That. TL;DR Five documented constraints decide an Odoo integration before anyone writes code. Odoo's JSON-2 reference states that external API access exists only on Custom pricing plans and not on One App Free or Standard. Odoo's acceptable use policy calls roughly one call per second, unsustained and with no parallel calls, acceptable on its cloud, and names dedicated Odoo.sh hosting as the way to lift that. Every call to the JSON-2 endpoint runs in its own SQL transaction and cannot be chained, which Odoo itself calls especially dangerous for reservations and payments, so any all-or-nothing operation belongs in a single method inside a custom module. The XML-RPC and JSON-RPC endpoints are scheduled for removal in Odoo 22 in fall 2028 and in Odoo Online 21.1 in winter 2027. And a database with custom modules cannot upgrade until those modules are compatible, while Odoo Online upgrades run on Odoo's schedule, with field renames such as res.users.groups_id becoming group_ids in 19.0 breaking clients that hardcode names. The shape that survives is one boundary service you own: an anti-corruption layer, your own read store fed by webhooks, a single serialized writer with idempotency keys, transaction-scoped custom methods for invariants, and contract tests against an upgraded copy of the database. Search for Odoo integration advice and you get two kinds of page: a partner brochure, or a tutorial that shows you how to call search_read from Python. Neither one tells you the thing that actually decides your architecture. Odoo publishes hard limits on what an external integration is allowed to do, and two of those limits have dates attached to them. This is written from the build side, for the team that has to connect a product, a customer portal, a shop, a pricing engine or an internal tool to an Odoo database and keep it working through Odoo's release train. It assumes Odoo stays. Nothing here is an argument for replacing an ERP that already runs your accounting. The only question worth answering is where the boundary sits between Odoo and the software you own, and Odoo's own documentation answers most of it if you read the right five pages. Answer first: five constraints decide an Odoo integration before anyone writes code. The external API is a paid-plan feature, not a platform feature. Odoo Cloud treats roughly one call per second with no parallel calls as acceptable use. Every JSON-2 call runs in its own transaction, so a multi-step business operation needs one server-side method, not four client calls. The old /xmlrpc, /xmlrpc/2 and /jsonrpc endpoints have a removal date. And a custom module blocks your upgrade while Odoo Online upgrades run on Odoo's schedule. Build the integration as one service you own, with its own store, idempotent writes and an event-driven inbound path, and four of the five stop being emergencies. The five limits, in one table LimitWhat Odoo statesWhat it forces on your design Plan gateExternal API access is available on Custom pricing plans, and not on One App Free or StandardThe API is a line item in the ERP contract, not a free capability. Confirm it before you scope the integration Call budgetRoughly one call per second, unsustained, with no parallel calls, is what the acceptable use policy calls acceptable on Odoo CloudBatch reads, aggregate server-side, and let Odoo push events to you instead of polling it Transaction boundaryEvery call to the JSON-2 endpoint runs in its own SQL transaction, and calls cannot be chained inside one transactionAny multi-step operation that must be all-or-nothing belongs in a single method inside a custom module Protocol clock/xmlrpc, /xmlrpc/2 and /jsonrpc are scheduled for removal in Odoo 22 (fall 2028) and Odoo Online 21.1 (winter 2027)New work targets /json/2. Existing RPC clients need a dated migration plan, not a backlog ticket Upgrade clockEach major version is supported for three years, Odoo Online upgrades are mandatory on a schedule, and a database with custom modules cannot upgrade until those modules are compatibleCustom code is a recurring maintenance liability with someone's name on it, and your integration tests are part of the upgrade gate Read the last two rows together, because they interact in the way that hurts. Your RPC client has to be gone by the time Odoo Online reaches 21.1, and the upgrade that takes you there is an upgrade you do not fully control. Limit 1: the external API is a plan feature The first surprise is commercial, not technical. Odoo's JSON-2 API reference states that access to data via the external API is only available on Custom pricing plans, and that it is not available on One App Free or Standard plans. That single note reorders a lot of projects. A company that picked the Standard plan because it looked like the sensible middle option has bought an ERP that its own",
  "articleSection": "Engineering",
  "author": {
    "@id": "https://wavect.io/team/kevin-riedl/#person",
    "@type": "Person",
    "name": "Kevin Riedl",
    "sameAs": [
      "https://www.wikidata.org/wiki/Q139796365",
      "https://www.linkedin.com/in/wsdt",
      "https://github.com/wsdt"
    ],
    "url": "https://wavect.io/team/kevin-riedl/"
  },
  "citation": [
    {
      "@type": "WebPage",
      "name": "JSON-2 API reference",
      "url": "https://www.odoo.com/documentation/19.0/developer/reference/external_api.html"
    },
    {
      "@type": "WebPage",
      "name": "acceptable use policy for Odoo Cloud",
      "url": "https://www.odoo.com/acceptable-use"
    },
    {
      "@type": "WebPage",
      "name": "automation rules",
      "url": "https://www.odoo.com/documentation/19.0/applications/studio/automated_actions.html"
    },
    {
      "@type": "WebPage",
      "name": "transaction section of the JSON-2 reference",
      "url": "https://www.odoo.com/documentation/19.0/developer/reference/external_api.html#transaction"
    },
    {
      "@type": "WebPage",
      "name": "External RPC API reference",
      "url": "https://www.odoo.com/documentation/19.0/developer/reference/external_rpc_api.html"
    },
    {
      "@type": "WebPage",
      "name": "upgrade documentation",
      "url": "https://www.odoo.com/documentation/19.0/administration/upgrade.html"
    },
    {
      "@type": "WebPage",
      "name": "became group_ids in the 19.0 source",
      "url": "https://github.com/odoo/odoo/blob/19.0/odoo/addons/base/models/res_users.py"
    },
    {
      "@type": "WebPage",
      "name": "hr.version",
      "url": "https://github.com/odoo/odoo/blob/19.0/addons/hr/models/hr_version.py"
    },
    {
      "@type": "WebPage",
      "name": "the LGPL version 3",
      "url": "https://github.com/odoo/odoo/blob/19.0/LICENSE"
    }
  ],
  "dateModified": "2026-08-18",
  "datePublished": "2026-08-18",
  "description": "Five documented constraints decide an Odoo integration before anyone writes code. Odoo's JSON-2 reference states that external API access exists only on Custom pricing plans and not on One App Free or Standard. Odoo's acceptable use policy calls roughly one call per second, unsustained and with no parallel calls, acceptable on its cloud, and names dedicated Odoo.sh hosting as the way to lift that. Every call to the JSON-2 endpoint runs in its own SQL transaction and cannot be chained, which Odoo itself calls especially dangerous for reservations and payments, so any all-or-nothing operation belongs in a single method inside a custom module. The XML-RPC and JSON-RPC endpoints are scheduled for removal in Odoo 22 in fall 2028 and in Odoo Online 21.1 in winter 2027. And a database with custom modules cannot upgrade until those modules are compatible, while Odoo Online upgrades run on Odoo's schedule, with field renames such as res.users.groups_id becoming group_ids in 19.0 breaking clients that hardcode names. The shape that survives is one boundary service you own: an anti-corruption layer, your own read store fed by webhooks, a single serialized writer with idempotency keys, transaction-scoped custom methods for invariants, and contract tests against an upgraded copy of the database.",
  "headline": "Odoo API integration: the five limits that decide your architecture",
  "image": "https://wavect.io/img/blog/headers/header_odoo-erp-api-integration-limits-2026.svg",
  "inLanguage": "en",
  "keywords": "Odoo, ERP Integration",
  "mainEntityOfPage": {
    "@id": "https://wavect.io/blog/odoo-erp-api-integration-limits-2026/",
    "@type": "WebPage"
  },
  "publisher": {
    "@id": "https://wavect.io/#organization",
    "@type": [
      "Organization",
      "ProfessionalService",
      "LocalBusiness"
    ]
  },
  "url": "https://wavect.io/blog/odoo-erp-api-integration-limits-2026/",
  "wordCount": 4562
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "item": "https://wavect.io/",
      "name": "Home",
      "position": 1
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/overview/",
      "name": "Blog overview",
      "position": 2
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/topics/delivery-qa/",
      "name": "Delivery and QA",
      "position": 3
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/clusters/architecture-platforms/",
      "name": "Architecture and platforms",
      "position": 4
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/odoo-erp-api-integration-limits-2026/",
      "name": "Odoo API Integration Limits: Rate Cap, JSON-2, Upgrades | ",
      "position": 5
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes. Odoo's External RPC API reference states that the XML-RPC and JSON-RPC APIs at the /xmlrpc, /xmlrpc/2 and /jsonrpc endpoints are scheduled for removal in Odoo 22 in fall 2028 and in Odoo Online 21.1 in winter 2027, with the External JSON-2 API as the replacement. The common, db and object services are all deprecated. Internal controllers declared with route type jsonrpc are explicitly outside that notice."
      },
      "name": "Is XML-RPC deprecated in Odoo?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "There is no published per-endpoint quota. Odoo's acceptable use policy for its cloud lists unthrottled RPC and API calls as prohibited abuse and says throttled calls are typically acceptable for unsustained usage at a rate of one call per second with no parallel calls. On Odoo.sh, dedicated hosting is named as the way to lift that restriction. Self-hosted databases are bounded by your own infrastructure instead."
      },
      "name": "What is the Odoo API rate limit?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "On Odoo's hosted plans, yes. The JSON-2 API reference states that access to data via the external API is only available on Custom pricing plans and is not available on One App Free or Standard plans. Self-hosted Community has no plan gate. Verify this against your own contract before you scope an integration, because a plan change prices per user per month."
      },
      "name": "Do I need a paid Odoo plan to use the external API?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. Odoo documents that every call to the JSON-2 endpoint runs in its own SQL transaction and that chaining multiple calls inside one transaction is not possible, so concurrent transactions can change the database between your calls. Odoo's own recommendation is to call a single method that performs all the related operations, and to create such a method in a dedicated module when none exists."
      },
      "name": "Can I run several Odoo API calls in one transaction?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Webhooks, in nearly every case. Polling is the largest consumer of a one-call-per-second budget and it scales with your customer count rather than your data volume. Odoo's automation rules include a Send Webhook Notification action that posts selected record fields to your URL. Put a queue behind your endpoint, return 200 quickly, and process asynchronously, because Odoo's own documentation warns that a badly configured webhook can disrupt the database."
      },
      "name": "Should I use webhooks or polling with Odoo?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "They block it until a compatible version of the module exists for the target Odoo version, which Odoo states in its upgrade documentation. Odoo also states that when a new version breaks a customization, making it compatible is the responsibility of that module's maintainer. Odoo's testing checklist puts integrations with external software and APIs first among the things to re-test."
      },
      "name": "Do custom modules block an Odoo upgrade?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Each major version is supported for three years. On Odoo Online an upgrade is mandatory every two years for a major version, and a few weeks after the next release for a minor version, with minor versions arriving roughly every two months. Odoo runs a silent test upgrade first, and if it succeeds and takes under twenty minutes the automatic upgrade proceeds unless you act before the deadline."
      },
      "name": "How often must an Odoo Online database be upgraded?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Field and model renames are the ones that reach external clients. In the 19.0 source, res.users.groups_id is group_ids, and the separate contract model moved into HR core as hr.version. A client that hardcodes old names fails at the field level rather than at the connection level, so a contract test against an upgraded copy of your own database is the only reliable detector."
      },
      "name": "What broke for integrations in Odoo 19?"
    },
    {
      "@type": "Question",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "The Odoo Community source ships under the LGPL version 3, which is why a proprietary module on top of Community is common practice rather than a legal argument. Enterprise editions and Odoo's hosted plans carry separate commercial terms, so check those against your specific case rather than generalising from Community."
      },
      "name": "Can I keep a proprietary custom module on Odoo Community?"
    }
  ]
}
```
