Enable Dark Mode!
overview-of-rest-api-integrations-in-odoo-19.jpg
By: Sayed Mahir Abdulla KK

Overview of REST API Integrations in Odoo 19

Technical Odoo 19 Odoo Community Odoo Enterprises

Some situations are universal for every Odoo project I work on. These are usually cases when the user wants to get or put data out/in Odoo outside of the Odoo UI. It can be an external mobile application that needs to know the inventory levels, or a third-party CRM system that has to transfer its data into Odoo, or a payment system that wants to send a webhook to Odoo upon a successful payment.

Odoo 19 framework changes mostly relate to the internal implementation of the models’ ORM methods. The public API mostly stayed the same. So if you are familiar with developing custom modules in Odoo, you are already most of the way there. For the Odoo 14 and Odoo 15 developers, the main API changes relate to the ways the models’ fields are declared (for example, access modifiers like invisible/readonly are now modelled as recordsets instead of being passed as strings in the ‘attrs’ dictionary) and some internal rewrites of the framework-related JSON routes machinery to make it more consistent.

The controller part (the http.Controller classes with @http.route decorators) has a public API that was not changed in the recent versions. This article will show how to create a new REST-like API endpoint based on an existing Odoo model and consume an external API from Odoo.

Why Odoo Does Not Have 'Real' REST by Default

Odoo's native web protocol is JSON-RPC 2.0 with an added layer of HTTP on top of it. When you call /web/dataset/call_kw from the browser, that's JSON-RPC, not REST, and it's not something a third-party system can be expected to speak natively. For external systems communicating with your Odoo instance, they most likely expect proper HTTP methods and RESTful-style URLs.

That means in practice, "REST API integration in Odoo" almost certainly means one of the following:

  • You can either wrote http.Controller routes which implement REST endpoints (translating HTTP methods to model crud operations)
  • You're calling an external REST API from somewhere in your Python code (a cron job, a button click, a webhook handler)
  • You're using the XML-RPC protocol to access Odoo's native API, which is an officially supported method requiring no custom code on your part, only some credential management and a library on the receiving end

How to make a REST API in Odoo 19

Let's say you have a custom model, library.book, and you want to expose a simple list and detailed views of it over HTTP for some external app to consume. For brevity's sake, I'll skip model definitions and the database setup, and only show the code necessary to expose it as a REST API.

# controllers/main.py
import json
from odoo import http
from odoo.http import request, Response

class LibraryAPIController(http.Controller):
   def _json_response(self, data, status=200):
       return Response(
           json.dumps(data),
           status=status,
           mimetype='application/json',
       )
   @http.route('/api/v1/books', type='http', auth='user',
               methods=['GET'], csrf=False)
   def list_books(self, **kw):
       domain = []
       if kw.get('available'):
           domain.append(('state', '=', 'available'))
       books = request.env['library.book'].search_read(
           domain,
           ['id', 'name', 'author_id', 'state'],
           limit=int(kw.get('limit', 50)),
           offset=int(kw.get('offset', 0)),
       )
       return self._json_response({'status': 'success', 'data': books})
   @http.route('/api/v1/books/<int:book_id>', type='http', auth='user',
               methods=['GET'], csrf=False)
   def get_book(self, book_id):
       book = request.env['library.book'].browse(book_id)
       if not book.exists():
           return self._json_response(
               {'status': 'error', 'message': 'Book not found'}, status=404
           )
       return self._json_response({
           'status': 'success',
           'data': {
               'id': book.id,
               'name': book.name,
               'author': book.author_id.name,
               'state': book.state,
           },
       })
   @http.route('/api/v1/books', type='http', auth='user',
               methods=['POST'], csrf=False)
   def create_book(self, **post):
       if not post.get('name'):
           return self._json_response(
               {'status': 'error', 'message': 'name is required'}, status=400
           )
       try:
           book = request.env['library.book'].create({
               'name': post['name'],
               'author_id': int(post['author_id']) if post.get('author_id') else False,
           })
           return self._json_response(
               {'status': 'success', 'id': book.id}, status=201
           )
       except Exception as e:
           return self._json_response(
               {'status': 'error', 'message': str(e)}, status=500
           )

A few things to look out for if you're actually implementing this:

  • auth='user' requires a valid Odoo session or authentication before making the call. In the case of machine-to-machine calls, this is generally not the way to go – you want API key auth instead (see below) since you don't want to handle any session cookies in your external system.
  • csrf=False is a must for all non-Odoo-originated POST, PUT, and DELETE calls. Not specifying this parameter will result in all POSTs failing silently with 403, and this particular detail caused me a major headache while working with one of my clients who insisted on the fact that the endpoint 'doesn't work at all'. It did – it just wasn't accepting any requests that were missing the Odoo CSRF token.
  • search_read is performing double duty here by reading and deserializing everything at once.

API Key Authentication for External Callers

When it comes to machine-to-machine communication, never rely on session authentication. The simple key validation via header will suffice to get most of what you need done:

class SecureAPIController(http.Controller):
   def _check_api_key(self):
       api_key = request.httprequest.headers.get('X-API-Key')
       if not api_key:
           return False
       valid_key = request.env['ir.config_parameter'].sudo().get_param(
           'library.api_key'
       )
       return api_key == valid_key
   @http.route('/api/v1/secure/books', type='http', auth='none', csrf=False)
   def secure_list_books(self, **kw):
       if not self._check_api_key():
           return Response(
               json.dumps({'error': 'Invalid API key'}),
               status=401, mimetype='application/json',
           )
       books = request.env['library.book'].sudo().search_read([], ['name', 'state'])
       return Response(
           json.dumps({'data': books}), mimetype='application/json',
       )

Take a look at auth='none' together with sudo() in the method: without a logged-in user, when using auth='none', each ORM call must use sudo() to avoid access restriction issues. While this approach is perfectly fine in a scoped and intentionally public API endpoint, it also requires you to provide all authorization logic manually. Odoo does nothing to prevent a malicious user with a leaked key from accessing all data in that model.

Consuming an External API from Odoo

The reverse direction - Odoo contacting an external API - tends to be a much easier task, as it's nothing but requests in a model method, cron, or a button handler.

import requests
import logging
_logger = logging.getLogger(__name__)

class LibraryBook(models.Model):
   _inherit = 'library.book'
   def action_fetch_metadata_from_isbn(self):
       for book in self:
           if not book.isbn:
               continue
           try:
               response = requests.get(
                   f'https://openlibrary.org/isbn/{book.isbn}.json',
                   timeout=10,
               )
               response.raise_for_status()
               data = response.json()
               book.write({
                   'name': data.get('title', book.name),
                   'page_count': data.get('number_of_pages'),
               })
           except requests.exceptions.RequestException as e:
               _logger.warning('ISBN lookup failed for %s: %s', book.isbn, e)

Two rules of thumb that will save you a debugging session: always set a timeout (external services may hang up and block the Odoo process) and catch RequestException rather than a general exception because, even if a call fails, you want your module and cron to keep working anyway.

Webhooks: The Reverse-REST Pattern

To get an external application to trigger events on Odoo (for example, a payment confirmation or an order update), you will create a webhook receiver that is technically identical to the REST controller above except for signature validation:

import hmac, hashlib
class WebhookController(http.Controller):
   @http.route('/webhook/payment', type='json', auth='none',
               methods=['POST'], csrf=False)
   def payment_webhook(self):
       data = request.jsonrequest
       signature = request.httprequest.headers.get('X-Signature')
       secret = request.env['ir.config_parameter'].sudo().get_param(
           'payment.webhook_secret'
       )
       expected = hmac.new(secret.encode(), json.dumps(data).encode(),
                            hashlib.sha256).hexdigest()
       if not hmac.compare_digest(signature or '', expected):
           return {'status': 'error', 'message': 'Invalid signature'}
       request.env['payment.transaction'].sudo()._handle_webhook(data)
       return {'status': 'success'}

hmac.compare_digest instead of == matters here because it's a constant-time comparison, which avoids leaking timing information that could theoretically help an attacker guess the signature byte by byte. Small things, easy to skip, worth doing anyway.

"Write an http.Controller, choose your authentication mechanism, and use sudo() carefully" is how REST integration looks in Odoo 19. There is no change in architecture because of version 19; the only difference is the update in the syntax of ORM. It’s the mundane things that make the REST integration work: handling timeouts when calling external systems, doing CSRF correctly on incoming calls, and using an API key that is not stored as plain text somewhere in some configuration variable.

To read more about How to Configure Odoo REST API Module in Odoo 18, refer to our blog How to Configure Odoo REST API Module in Odoo 18.


Frequently Asked Questions

Does Odoo 19 have a built-in REST API, or do I have to build one myself?

Odoo doesn't ship a generic REST API out of the box. It has JSON-RPC and XML-RPC for external system access, and you build REST-style endpoints yourself using http.Controller and @http.route.

What's the difference between type='http' and type='json' routes?

type='http' routes return a raw HTTP response you construct yourself (good for REST-style JSON, file downloads, or plain text). type='json' routes expect and return JSON-RPC formatted payloads and are what Odoo's own web client uses internally.

Why does my POST request to a custom controller return a 403 error?

Almost always CSRF protection. Odoo enables CSRF by default on http routes, and external clients won't have a valid Odoo CSRF token. Set csrf=False on the route — but only do this for endpoints that have their own authentication.

Should I use auth='user', auth='public', or auth='none' for an API endpoint?

auth='user' if the caller already has an Odoo session (rare for external integrations). auth='public' if unauthenticated users should be able to hit it as the public user. auth='none' for machine-to-machine APIs where you're handling authentication yourself, like an API key.

Can I use XML-RPC instead of building a custom REST API?

Yes, and for many integrations it's less work. Odoo's XML-RPC endpoints (/xmlrpc/2/common, /xmlrpc/2/object) let an external system authenticate and call any model method Odoo already exposes, without you writing a single controller. It's a good fit when the other system just needs standard CRUD access and can work with XML-RPC libraries.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



WhatsApp