RFID and IoT Integration with Odoo
Odoo's stock model is specific about how inventory is allowed to move. A practical guide to the external API, the records a tag read should actually write, and the hosting decision that constrains the whole design.
Odoo is the most open of the major ERP platforms to integrate with, and the easiest one to integrate with badly. The external API will let you write almost anything, including records you have no business writing directly, and the resulting inventory will look correct for about a week. The discipline is not in getting access; it is in writing only the records Odoo's stock model expects you to write.
Hosting decides the design before anything else
Odoo runs in three broadly different ways, and the choice constrains your integration more than any other single factor.
- Odoo Online, the vendor-hosted service, does not allow custom code to be installed. That rules out a purpose-built addon, and with it any server-side endpoint of your own design. The integration has to work entirely through the external API from outside.
- Odoo.sh, the vendor's managed platform, does allow custom addons, deployed through a Git-based workflow.
- Self-hosted Odoo, Community or Enterprise, allows anything you are willing to maintain.
Establish which one you are dealing with in the first conversation. A design that assumes a custom module is not a small revision away from one that cannot have a custom module. It is a different design.
Version matters too. Odoo ships a major release annually and model names do move between them; inventory code written against an older release should be reviewed rather than assumed to run. Confirm the version and the upgrade cadence up front.
The interfaces available
| Interface | Suited to | Worth knowing |
|---|---|---|
| XML-RPC | The documented external API; read and write against any model the user may access | Works everywhere, including Odoo Online; verbose on the wire and not fast per call |
| JSON-RPC | The same capability over a JSON transport | Usually the more comfortable option for modern clients |
| A controller in a custom addon | Purpose-built endpoints that accept a batch of reads and do the work server-side | Requires the ability to install code, so not available on Odoo Online |
| Automated actions and server actions | Reacting to changes inside Odoo without an external service | Useful for small rules, awkward for anything that needs testing |
| Asynchronous job queueing | Absorbing bursts rather than processing them in the request | Commonly added through a community module rather than present by default |
For authentication, use an API key rather than a stored user password. Odoo supports issuing keys for exactly this purpose, and a key can be revoked without disrupting a person's login. The integration user should be a dedicated account with the narrowest access rights that let it do its job, not an administrator.
The stock model you are writing into
Odoo's inventory is a ledger with a projection on top of it, and that distinction is the whole game.
stock.quantholds on-hand quantity by product, location, lot and package. It is largely a projection of movement history. Writing to it directly to "correct" a quantity is the classic Odoo integration mistake: the number changes, no move explains why, and traceability and valuation are quietly wrong from that moment on. The supported way to adjust stock is the inventory adjustment mechanism, which produces a real move.stock.pickingis a transfer: a receipt, a delivery, an internal movement. This is normally the object an RFID workflow is completing.stock.moveis the demand line;stock.move.lineis the detailed operation that actually happened, and it is where lot, package, source location, destination location and quantity live. Writing move lines and then validating the picking is the honest way to make a transfer occur, and it is precisely what the user interface does when someone scans.stock.lotcarries lot and serial identity. For serial-tracked products this is the natural counterpart to a unique tag.stock.quant.packagerepresents a pallet, tote or container, and is usually the right home for a pallet-level tag.stock.locationis what a read zone maps onto. Getting that mapping right is what makes the resulting move mean something.
For work in progress, manufacturing orders are the equivalent target; for asset and equipment tracking, the maintenance equipment model. The principle does not change: find the record the application already uses for that concept and write that, rather than inventing a shadow table.
A reference architecture
- Readers publish to an edge service on the local network. Odoo never sees a raw read.
- The edge deduplicates, applies read-zone and direction rules, and discards tags that were present but not moving.
- It resolves EPC to Odoo identity: a package, a lot, or a product and quantity.
- It groups the surviving events into one transfer-shaped operation rather than a stream of individual writes.
- It writes move lines against the relevant picking and validates it, or submits an inventory adjustment for a count.
- Each submission carries a key derived from the physical event, so a retry after a timeout is recognised rather than duplicated.
- Anything unresolved goes to an exception queue with the read context attached.
Decisions to settle before anyone writes code
- Hosting model, because it determines whether server-side code is even possible.
- Version, and who is responsible for retesting the integration at the next upgrade.
- The tracking policy on each product in scope: untracked, by lot, or by unique serial. This decides whether a tag can map to a lot record at all.
- Package or lot as the unit of identity for the tag, which usually follows from whether you are tracking containers or items.
- Who validates a transfer: the integration automatically, or a person confirming on screen.
- Multi-company and multi-warehouse rules, which silently filter what the integration user can see.
Where these integrations usually go wrong
- Writing
stock.quantdirectly. The fastest route to inventory that reconciles to nothing. Use adjustments and moves. - One remote call per read. Odoo's RPC layer is not built for a burst of hundreds of small calls. Batch at the edge and submit grouped operations.
- Running as an administrator with a stored password. Use a dedicated user and an API key, scoped to what the workflow needs.
- Ignoring company and access rules. A misconfigured integration user does not error. It returns nothing, which is considerably harder to debug.
- Validating against demonstration data. A demonstration database tells you nothing about behaviour at real volume with real product configuration.
- Treating an upgrade as someone else's problem. Odoo moves quickly. Build the integration so that a version change is a testing exercise, not an archaeology project.
How we scope an Odoo integration
We begin with the hosting model, the version and the tracking configuration, because those three settle most of the design space before any code exists. Then the physical workflow: which movements are in scope, where the read zones can realistically go, and what the operation does when an item does not read. The integration itself is usually the least difficult part of the project, which is exactly why it should not be the first thing decided.
Related reading
Discuss an ERP integration
Continue