Systems
Odoo integration with your online store: orders in, stock per warehouse out
Since Odoo 19 an integration talks to Odoo over the JSON-2 API with an API key that expires after at most 90 days for a normal user, and XML-RPC is removed in Odoo 22. We build the integration on your Odoo version, plan and apps, then host and run it.
What Odoo is and who runs it
Odoo is an ERP from Belgium built as separate apps: Sales, Purchase, Inventory, Accounting, Manufacturing, CRM and its own website and store. There is an open-source Community edition and a paid Enterprise edition on subscription. Each installed app adds its own models and fields, and Odoo documents the API per database for that reason: two Odoo databases do not offer the same API.
Two things decide what an integration can do: where Odoo runs and which version it is.
| Hosting | Custom modules | Versions |
|---|---|---|
| Odoo Online | none, also no Apps Store modules | major versions plus SaaS versions every 2 to 3 months |
| Odoo.sh | yes | major versions |
| Own server | yes | major versions, extended support available |
Odoo ships a major version every fall with 3 years of standard support. Odoo 20.0 came out in September 2026, 19.0 in September 2025 and 18.0 in October 2024. An integration built today usually runs on 18 or 19 and goes through an upgrade within two years.
What connects to Odoo
In most setups Odoo owns products, stock, prices and invoices. The store owns the order and the new customer.
| Flow | Direction | Odoo model |
|---|---|---|
| Orders | store to Odoo | sale.order with order_line |
| Customers and addresses | store to Odoo | res.partner |
| Products and variants | Odoo to store | product.template, product.product |
| Stock | Odoo to store | free_qty on product.product; stock.quant |
| Price lists | Odoo to store | product.pricelist, product.pricelist.item |
| Shipping and tracking | Odoo to store | stock.picking, field carrier_tracking_ref |
| Invoices | Odoo to store | account.move |
An order goes in with its lines in one call. Customer and product travel as Odoo IDs, so the integration looks them up first. This is order SHOP-100482 from Harbor Supply Co. on Odoo 19:
POST /json/2/sale.order/create HTTP/1.1
Host: harborsupply.odoo.com
Authorization: bearer 3f9c...e1a7
Content-Type: application/json; charset=utf-8
{
"vals_list": [{
"partner_id": 4711,
"partner_shipping_id": 4712,
"client_order_ref": "SHOP-100482",
"order_line": [[0, 0, {
"product_id": 881, "product_uom_qty": 12,
"price_unit": 17.08, "tax_ids": [[6, 0, [17]]]
}]]
}]
}
The three-element lists are Odoo's write commands for related records: 0 creates a line, 6 replaces the set of taxes. Over the API they have to be sent literally, as numbers. The store order number goes into client_order_ref, the Customer Reference field. The fields per flow are on order integration.
How Odoo exposes data
JSON-2 from Odoo 19, XML-RPC before it
| Version | External API | Authentication |
|---|---|---|
| Odoo 17 and 18 | XML-RPC and JSON-RPC on /xmlrpc/2 and /jsonrpc |
database, login, password or API key |
| Odoo 19 | JSON-2 on /json/2/<model>/<method>; RPC deprecated |
API key as bearer token |
| Odoo 20 | JSON-2; RPC database service removed | API key as bearer token |
| Odoo 22 (fall 2028) | JSON-2 only | API key as bearer token |
Both APIs reach the same models and methods Odoo uses internally: search_read, create, write, and the buttons on a form, such as action_confirm on a sales order. Which models and fields a database has depends on the installed apps. Methods whose name starts with an underscore cannot be called over the API. On a server with several databases the database name goes in the X-Odoo-Database header.
API keys that expire
An API key belongs to a user and Odoo shows it once. In Odoo 19 every key has an expiry date, and for a normal internal user the maximum is 90 days; only an administrator can create a key without one. Odoo recommends a separate bot user for an integration, with only the access rights it needs. A 90-day key means the integration stops after three months unless something replaces the key. Odoo provides res.users.apikeys/generate for that: the integration creates a new key with the current one and revokes the old one. By default a user may hold 10 keys created this way; the 11th returns HTTP 422.
Only on the Custom plan
Odoo states that data access through the external API is only available on Custom plans, not on One App Free or Standard. A store integration is external API use. The JSON-2 API documentation lists the plan rule, the authentication and the error responses.
Every call is its own transaction
Odoo's external API documentation gives no request limit per minute. What it does say: every JSON-2 call runs in its own database transaction, and chaining several calls into one transaction is not possible. An order therefore takes two transactions. create makes a quotation and action_confirm turns it into a sales order. When the second call fails, a quotation with the store order number stays behind.
The arithmetic for 600 orders on a busy day: one lookup for the customer, one for the products of all lines together, one create and one action_confirm. That is 4 calls per order and 2,400 a day. Reads are paged: 15,000 variants through search_read with a limit of 1,000 is 15 calls. How fast Odoo answers depends on hosting and installed apps, so we measure it on your database before go-live. The general pattern is on integration throughput.
Where an Odoo integration gets stuck
| What the store needs | In Odoo | Without a rule |
|---|---|---|
| One unique SKU per variant | default_code on product.product, not enforced unique |
two variants share a code, the order lands on the wrong one |
| Sellable stock | qty_available, free_qty, virtual_available |
stock in the store too high |
| Changed stock since the last run | computed fields, no write_date change on the product |
stock changes missed |
| Customer price | rules in product.pricelist.item, no public method |
wrong price in a B2B store |
| A field name after an upgrade | tax_id became tax_ids in Odoo 19 |
every order fails after the upgrade |
Variants and a code that is not unique. A product in Odoo is a product.template and each variant is a product.product. The store SKU belongs on the variant, in default_code (Internal Reference). Odoo warns about a duplicate code on the form and accepts it over the API. The barcode field is unique per company. The integration checks before each run that a SKU resolves to exactly one variant and puts a duplicate on the exception list. See variant mapping.
Three stock numbers and no change date. qty_available is what is physically there. free_qty is that minus what is reserved for confirmed orders. virtual_available adds planned receipts and deliveries. The store usually needs free_qty. With warehouse_id or location in the call context Odoo returns the number for one warehouse or location; without it, all internal locations are added up. All three are computed, so a stock move does not change the product's write_date, and an integration that filters products on write_date misses every stock change. We read changes from stock.quant instead. Odoo stores datetimes in UTC without a time zone, so a filter for midnight in New York during daylight saving time is 04:00:00 UTC. See which stock counts.
Customer prices. The price for a customer comes from that customer's price list: rules with a fixed price, a discount or a formula, a min_quantity and a date_start. The methods that turn those rules into a price start with an underscore and cannot be called over the API. The integration reads the rules and computes the price itself, or on Odoo.sh and a self-hosted server a small module does it. See customer-specific pricing.
Field names that change per version. The tax field on an order line was tax_id up to Odoo 18 and is tax_ids in Odoo 19. An integration that keeps sending the old name fails on every order after the upgrade. Before an upgrade the integration therefore runs as a dry run against a copy of the database. On Odoo Online that is a duplicate: at most 5 per database, valid for 15 days, and with "For testing purposes" checked, emails, payments and deliveries are switched off in the copy.
Platforms we integrate with Odoo
The integration is built on your Odoo, with your apps, price lists and warehouses. The store side is described per platform on Shopify, WooCommerce and Magento. For the ERP side in general see e-commerce ERP integration; other ERPs we integrate include Business Central and NetSuite.
We build the integration for a fixed price, then host, run and monitor it for a monthly fee. Before go-live it runs as a dry run: you see per order which customer, variants and taxes would be created in Odoo, without anything being written. All integrations come with a dashboard, logs and a per-run diff. Tell us your Odoo version and plan.
Frequently asked questions
Does the integration work with Odoo Online, Odoo.sh and a self-hosted server?
Yes. All three expose the same external API. The difference is what runs next to it: Odoo Online accepts no custom modules and no modules from the Odoo Apps Store, so everything goes through the standard models and public methods. On Odoo.sh and your own server a small module can be added, for example to compute a customer price inside Odoo.
Do I need the Odoo Custom plan?
Odoo's documentation says access to data through the external API is only available on Custom plans, not on One App Free or Standard. A store integration that reads and writes Odoo from outside is external API use. Calls that Odoo itself makes to a payment provider or carrier are not counted as external API on Odoo's pricing page.
Which Odoo version do I need?
Any version in standard support. Odoo 19 and 20 have the JSON-2 API; Odoo 17 and 18 have XML-RPC and JSON-RPC only. Standard support for Odoo 17 ended in September 2026 and runs to September 2027 for Odoo 18. A database on 17 or 18 will be upgraded soon, and the upgrade changes the API and some field names.
What happens to an XML-RPC integration when Odoo removes it?
The database service of XML-RPC and JSON-RPC is already gone in Odoo 20. The common and object services, which integrations use, are removed in Odoo 22, planned for fall 2028, and on Odoo Online in version 21.1, winter 2027. An integration on /xmlrpc/2/object has to move to /json/2 before then, and from a login and password to an API key in the header.
Can Odoo send a webhook when something changes?
Yes, through an automation rule with the action Send Webhook Notification, which POSTs the fields you select to a URL. Odoo's documentation describes no retries when the receiver is down. We treat the webhook as a signal and run a reconciliation pass on write_date next to it, so a missed call does not become a missed order. See why webhooks are not a guarantee.
Odoo has its own eCommerce app. Why an integration?
With Odoo eCommerce the store and the ERP are one database and nothing needs integrating. This page is about a store on another platform, such as Shopify, WooCommerce or Magento, with Odoo behind it. Then there are two systems, each with its own product ID, customer and order, and the mapping between them is the work.
Tell us what you need
Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.
- You know the price and the timeline before you commit to anything
- We work out the connection that takes the work off your hands
- We handle the build, hosting, monitoring and maintenance
- Your team has less information to retype and fewer discrepancies to chase