To add your own REST service you must provides at least 2 classes. * A Component providing the business logic of your service, * A Controller to register your service. The business logic of your service must be implemented into a component (``odoo.addons.component.core.Component``) that inherit from 'base.rest.service' Initially, base_rest expose by default all public methods defined in a service. The conventions for accessing methods via HTTP were as follows: * The method ``def get(self, _id)`` if defined, is accessible via HTTP GET routes ``/`` and ``//get``. * The method ``def search(self, **params)`` if defined, is accessible via the HTTP GET routes ``/`` and ``/search``. * The method ``def delete(self, _id)`` if defined, is accessible via the HTTP DELETE route ``/``. * The ``def update(self, _id, **params)`` method, if defined, is accessible via the HTTP PUT route ``/``. * Other methods are only accessible via HTTP POST routes ```` or ``/`` or ``/`` or ``//`` .. code-block:: python from odoo.addons.component.core import Component class PingService(Component): _inherit = 'base.rest.service' _name = 'ping.service' _usage = 'ping' _collection = 'my_module.services' # The following method are 'public' and can be called from the controller. def get(self, _id, message): return { 'response': 'Get called with message ' + message} def search(self, message): return { 'response': 'Search called search with message ' + message} def update(self, _id, message): return {'response': 'PUT called with message ' + message} # pylint:disable=method-required-super def create(self, **params): return {'response': 'POST called with message ' + params['message']} def delete(self, _id): return {'response': 'DELETE called with id %s ' % _id} # Validator def _validator_search(self): return {'message': {'type': 'string'}} # Validator def _validator_get(self): # no parameters by default return {} def _validator_update(self): return {'message': {'type': 'string'}} def _validator_create(self): return {'message': {'type': 'string'}} Once you have implemented your services (ping, ...), you must tell to Odoo how to access to these services. This process is done by implementing a controller that inherits from ``odoo.addons.base_rest.controllers.main.RestController`` .. code-block:: python from odoo.addons.base_rest.controllers import main class MyRestController(main.RestController): _root_path = '/my_services_api/' _collection_name = my_module.services In your controller, _'root_path' is used to specify the root of the path to access to your services and '_collection_name' is the name of the collection providing the business logic for the requested service/ By inheriting from ``RestController`` the following routes will be registered to access to your services .. code-block:: python @route([ ROOT_PATH + '', ROOT_PATH + '/search', ROOT_PATH + '/', ROOT_PATH + '//get' ], methods=['GET'], auth="user", csrf=False) def get(self, _service_name, _id=None, **params): method_name = 'get' if _id else 'search' return self._process_method(_service_name, method_name, _id, params) @route([ ROOT_PATH + '', ROOT_PATH + '/', ROOT_PATH + '/', ROOT_PATH + '//' ], methods=['POST'], auth="user", csrf=False) def modify(self, _service_name, _id=None, method_name=None, **params): if not method_name: method_name = 'update' if _id else 'create' if method_name == 'get': _logger.error("HTTP POST with method name 'get' is not allowed. " "(service name: %s)", _service_name) raise BadRequest() return self._process_method(_service_name, method_name, _id, params) @route([ ROOT_PATH + '/', ], methods=['PUT'], auth="user", csrf=False) def update(self, _service_name, _id, **params): return self._process_method(_service_name, 'update', _id, params) @route([ ROOT_PATH + '/', ], methods=['DELETE'], auth="user", csrf=False) def delete(self, _service_name, _id): return self._process_method(_service_name, 'delete', _id) As result an HTTP GET call to 'http://my_odoo/my_services_api/ping' will be dispatched to the method ``PingService.search`` In addition to easily exposing your methods, the module allows you to define data schemas to which the exchanged data must conform. These schemas are defined on the basis of `Cerberus schemas `_ and associated to the methods using the following naming convention. For a method `my_method`: * ``def _validator_my_method(self):`` will be called to get the schema required to validate the input parameters. * ``def _validator_return_my_method(self):`` if defined, will be called to get the schema used to validate the response. In order to offer even more flexibility, a new API has been developed. This new API replaces the implicit approach used to expose a service by the use of a python decorator to explicitly mark a method as being available via the REST API: ``odoo.addons.base_rest.restapi.method``. .. code-block:: python class PartnerNewApiService(Component): _inherit = "base.rest.service" _name = "partner.new_api.service" _usage = "partner" _collection = "base.rest.demo.new_api.services" _description = """ Partner New API Services Services developed with the new api provided by base_rest """ @restapi.method( [(["//get", "/"], "GET")], output_param=restapi.CerberusValidator("_get_partner_schema"), auth="public", ) def get(self, _id): return {"name": self.env["res.partner"].browse(_id).name} def _get_partner_schema(self): return { "name": {"type": "string", "required": True} } @restapi.method( [(["/list", "/"], "GET")], output_param=restapi.CerberusListValidator("_get_partner_schema"), auth="public", ) def list(self): partners = self.env["res.partner"].search([]) return [{"name": p.name} for p in partners] Thanks to this new api, you are now free to specify your own routes but also to use other object types as parameter or response to your methods. For example, `base_rest_datamodel` allows you to use Datamodel object instance into your services. .. code-block:: python from marshmallow import fields from odoo.addons.base_rest import restapi from odoo.addons.component.core import Component from odoo.addons.datamodel.core import Datamodel class PartnerSearchParam(Datamodel): _name = "partner.search.param" id = fields.Integer(required=False, allow_none=False) name = fields.String(required=False, allow_none=False) class PartnerShortInfo(Datamodel): _name = "partner.short.info" id = fields.Integer(required=True, allow_none=False) name = fields.String(required=True, allow_none=False) class PartnerNewApiService(Component): _inherit = "base.rest.service" _name = "partner.new_api.service" _usage = "partner" _collection = "base.rest.demo.new_api.services" _description = """ Partner New API Services Services developed with the new api provided by base_rest """ @restapi.method( [(["/", "/search"], "GET")], input_param=restapi.Datamodel("partner.search.param"), output_param=restapi.Datamodel("partner.short.info", is_list=True), auth="public", ) def search(self, partner_search_param): """ Search for partners :param partner_search_param: An instance of partner.search.param :return: List of partner.short.info """ domain = [] if partner_search_param.name: domain.append(("name", "like", partner_search_param.name)) if partner_search_param.id: domain.append(("id", "=", partner_search_param.id)) res = [] PartnerShortInfo = self.env.datamodels["partner.short.info"] for p in self.env["res.partner"].search(domain): res.append(PartnerShortInfo(id=p.id, name=p.name)) return res The BaseRestServiceContextProvider provides context for your services, including authenticated_partner_id. You are free to redefine the method _get_authenticated_partner_id() to pass the authenticated_partner_id based on the authentication mechanism of your choice. See base_rest_auth_jwt for an example. In addition, authenticated_partner_id is available in record rule evaluation context.