How to Call Odoo API with PHP

Odoo has provided an API that can be accessed from outside of the odoo application for all model by default. So if we want to link our odoo application with other application we don’t need to create our own API. Just call the API that has been provided by odoo. In this tutorial, I will discuss how to call odoo API using the PHP programming language.

Odoo itself already has documentation about how to use their API, please look here. But from the documentation, I think several things are not included. Like the different ways of how to call a method with the @api.model decorator and a method with the @api.multi decorator. Therefore I will write this tutorial based on the odoo documentation with some modifications.

To call odoo API from PHP, we need xml rpc client, in the documentation, odoo uses Ripcord, please download ripcord here .

Authentication

To call odoo API, we need a user id. Therefore, we need to call the authenticate method first to get the user id, if you already know the id of a user that you want to use to call odoo API, you may not need to call the authenticate method.

To perform authentication we need to provide these parameters.

  1. URL / Server Address

    Fill with the odoo server address, including the port if exist. For example http://33.33.33.33:8069 or https://ngasturi.id:8069. Because in this tutorial my odoo server is located on a local machine, so I fill in http://localhost:8069.

  2. Database Name

    Fill in the database name where we will manipulate the data. Please go to server_address/web/database/selector to see full list of available database names.

    Select odoo database

    From the picture above, it can be seen that I have 2 databases. I will use the tutorial_dua database.

  3. User Email and Password

    The email and password here are the email used to sign in to odoo via a web browser. So if we can use the email and password to sign in to odoo via a web browser, the email and password could also be used to call the odoo API.

    We must remember that the permissions still in use when we call odoo API. For example, if the user cannot create a Sales Order via Odoo User Interface (web browser), of course, he cannot create a Sales Order via the API.

For example, assume I have a user like in the image below.

Master user in odoo

You can see that the email of the user above is agus@gmail.com and his id (primary key) is 6 (please take a look at the id parameter in the browser address bar). Let’s assume, if his password is 123, and if we call the authenticate method, it should return the user’s id or user’s primary key, namely 6. Here is the PHP code to perform authentication.

<?php 
	require_once('ripcord-master/ripcord.php');

	$url = 'http://localhost:8069';
	$db = 'tutorial_dua';
	$email = 'agus@gmail.com';
	$password = '123';

	$common = ripcord::client("$url/xmlrpc/2/common");
	$uid = $common->authenticate($db, $email, $password, []);
	
	if(!empty($uid)){
		echo "Successfully sign in with the user id of : " . $uid . '</br>';
	}else{
		echo "Failed to sign in";
	}
?>

Basically, when we call the odoo API, we execute the public methods of a model. Therefore I recommend you to read the odoo source code, to see the list of public methods that available, that can be executed via the API.

This is the standard format to perform the odoo API call.

$models = ripcord::client("$url/xmlrpc/2/object");
$result = $models->execute_kw(db_name, user_id, user_password, model_name, public_method_name, record_ids, positional_argument, keyword_argument);

This is the explanation of the arguments that we enter in the execute_kw method above.

  1. db_name

    The name of the database where we will manipulate the data. It’s the same as when we did authentication. Its data type is a string.

  2. user_id

    The user id that we get when performing authentication. If you already know the user id, you don’t need to do authentication. Its data type is an integer.

  3. user_password

    The password that is used to sign in by the user via the odoo user interface. Same as the password used when performing authentication. Its data type is a string.

  4. model_name

    The name of the model that we will manipulate the data. To see the full list of models in odoo, go to the Settings >> Technical >> Database Structure >> Models menu (you have to enter the debug mode beforehand). Its data type is a string.

  5. public_method_name

    To find out the name of the method we must read the odoo source code. For example this is the source code for the odoo model written in python . All methods in the BaseModel class in that file can be called from any model in odoo.

    In python, public methods are methods that not start with underscore, for example, the search method in the odoo source code on the link above, as shown below.

    @api.model
    @api.returns('self',
        upgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else self.browse(value),
        downgrade=lambda self, value, args, offset=0, limit=None, order=None, count=False: value if count else value.ids)
    def search(self, args, offset=0, limit=None, order=None, count=False):
    

    The search method is used to find some data in the database, where this method will return the id of a model that matches the search criteria that wrapped in an array. This is an example code of how to call the search public method on the res.partner model.

    <?php 
    	require_once('ripcord-master/ripcord.php');
    
    	$url = 'http://localhost:8069';
    	$db = 'tutorial_dua';
    	$email = 'agus@gmail.com';
    	$password = '123';
    
    	$common = ripcord::client("$url/xmlrpc/2/common");
    	$uid = $common->authenticate($db, $email, $password, []);
    	
    	if(!empty($uid)){
    		echo "Successfully sign in with the user id of : " . $uid . '</br>';
    
    		$models = ripcord::client("$url/xmlrpc/2/object");
    
    		// an example of how to call 'search' the public method
    		$partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', [[]]);
    
    		echo "<pre>" . print_r($partners, true) . "</pre>";
    
    	}else{
    		echo "Failed to sign in";
    	}
    ?>
    

    The result will look like this.

    Odoo API call result in PHP

    Private methods are methods that start with underscore. For comparison, there is a _search private method in the same file that looks like as below.

    @api.model
    def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
    

    If we call the private methods like the code below.

    <?php 
    	require_once('ripcord-master/ripcord.php');
    
    	$url = 'http://localhost:8069';
    	$db = 'tutorial_dua';
    	$email = 'agus@gmail.com';
    	$password = '123';
    
    	$common = ripcord::client("$url/xmlrpc/2/common");
    	$uid = $common->authenticate($db, $email, $password, []);
    	
    	if(!empty($uid)){
    		// echo "Successfully sign in with the user id of : " . $uid . '</br>';
    
    		$models = ripcord::client("$url/xmlrpc/2/object");
    
    		// an example of how to call '_search' the private method, it should cause an error
    		$partners = $models->execute_kw($db, $uid, $password, 'res.partner', '_search', [[]]);
    
    		echo "<pre>" . print_r($partners, true) . "</pre>";
    
    	}else{
    		echo "Failed to sign in";
    	}
    ?>
    

    It will return an error like the image below

    Error when call private method of odoo API
  6. record_ids

    Whether we must fill this argument or not depends on the decorator of a method that we call. If a method begins with an @api.model or @api.model_create_multi decorator, we don’t need to fill this argument, if we fill it, it will return an error. The search method above uses the @api.model decorator so we don’t need to fill this argument.

    If a method does not start with the @api.model or @api.model_create_multi decorator we must fill these arguments. For example the write method below (still in the odoo source code on the link above).

    def write(self, vals):
    

    The write method is used to edit some value of a model. For example, let’s assume we have 2 partners (customers) with id of 9 and 10. If we want to change the address of that two partners with the same value we can use the code as below.

    <?php 
    	require_once('ripcord-master/ripcord.php');
    
    	$url = 'http://localhost:8069';
    	$db = 'tutorial_dua';
    	$email = 'agus@gmail.com';
    	$password = '123';
    
    	$common = ripcord::client("$url/xmlrpc/2/common");
    	$uid = $common->authenticate($db, $email, $password, []);
    	
    	if(!empty($uid)){
    		echo "Successfully sign in with the user id of : " . $uid . '</br>';
    
    		$models = ripcord::client("$url/xmlrpc/2/object");
    
    		$partner_record_ids = [9,10];
    		$partner_value = [
    			'street' => 'Jl. Rajawali 12',
    			'city' => 'Surabaya'
    		];
    		$values = [$partner_record_ids, $partner_value];
    
    		$partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'write', $values);
    
    		echo "<pre>" . print_r($partners, true) . "</pre>";
    
    	}else{
    		echo "Failed to sign in";
    	}
    ?>
    

    From the code above, we can see that the value of record_ids is an integer wrapped in an array. Then we wrap the array again together with the positional_argument. Please remember that record_ids must be placed before the positional_argument.

  7. positional_argument

    Positional arguments are variables that we fill in a method sequentially according to the order/position of the arguments written in the original source code. For example in the search method above the first 3 arguments / parameters are args offset and limit, if we want to set a value of limit then we also have to set the offset value regardless of whether we want to set the offset value or not. Because the offset is written first, while the args parameter is required because there is no default value. These positional arguments sometimes have to be preceded by record_ids which is the id or primary key of a record. See the previous explanation.

    <?php 
    	require_once('ripcord-master/ripcord.php');
    
    	$url = 'http://localhost:8069';
    	$db = 'tutorial_dua';
    	$email = 'agus@gmail.com';
    	$password = '123';
    
    	$common = ripcord::client("$url/xmlrpc/2/common");
    	$uid = $common->authenticate($db, $email, $password, []);
    	
    	if(!empty($uid)){
    		$models = ripcord::client("$url/xmlrpc/2/object");		
    
    		// an example of how to call odoo API with positional argument
    		$partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', [[],0,2]);
    
    		echo "<pre>" . print_r($partners, true) . "</pre>";
    
    	}else{
    		echo "Failed to sign in";
    	}
    ?>
    
  8. keyword_argument

    Keyword arguments are arguments that have a default value. Keyword arguments can be written together with a positional argument or written separately. If written together with positional arguments, we must pay attention to the order in which these arguments are written in the odoo source code. If written separately, we can ignore the sequence and write only the arguments we need. Keyword arguments are written in an associative array as shown below.

    <?php 
    	require_once('ripcord-master/ripcord.php');
    
    	$url = 'http://localhost:8069';
    	$db = 'tutorial_dua';
    	$email = 'agus@gmail.com';
    	$password = '123';
    
    	$common = ripcord::client("$url/xmlrpc/2/common");
    	$uid = $common->authenticate($db, $email, $password, []);
    	
    	if(!empty($uid)){
    		$models = ripcord::client("$url/xmlrpc/2/object");
    
    		// an example of how to call the odoo API with keyword argument
    		$partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', [[]], ['order' => 'name desc','limit' => 2]);
    
    		echo "<pre>" . print_r($partners, true) . "</pre>";
    
    	}else{
    		echo "Failed to sign in";
    	}
    ?>
    

General Method

These are some general methods in odoo that can be called from any model.

  1. Create

    This method is used to insert data into the database. In odoo source code this method look like this.

    @api.model_create_multi
    @api.returns('self', lambda value: value.id)
    def create(self, vals_list):
    

    The create method uses the @api.model_create_multi decorator so we don’t need to fill the record_ids. This method only consists of one parameter in the form of an associative array. This method will return the primary key or id of the data that was successfully inserted. This is an example of how to use the create method to create the master partner (customer).

    <?php 
    	require_once('ripcord-master/ripcord.php');
    
    	$url = 'http://localhost:8069';
    	$db = 'tutorial_dua';
    	$email = 'agus@gmail.com';
    	$password = '123';
    
    	$common = ripcord::client("$url/xmlrpc/2/common");
    	$uid = $common->authenticate($db, $email, $password, []);
    	
    	if(!empty($uid)){
    		$models = ripcord::client("$url/xmlrpc/2/object");
    
    		// an example of how to call the create method in res.partner model
    		$values = [
    			'name' => 'Ngasturi',
    			'street' => 'Jl. Semolowaru 12',
    			'city' => 'Surabaya'
    		];
    		$partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'create', [$values]);
    
    		echo "<pre>" . print_r($partners, true) . "</pre>";
    
    	}else{
    		echo "Failed to sign in";
    	}
    ?>
    
  2. Write

    This method is used to update the data in the database. Similar to the create method, this method also contain one parameter which its value is an associative array, but we must fill the record_ids. Please see the previous code in the record_ids section.

  3. Copy

    This method is used to duplicate the data in the database. In odoo source code this method look like this

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
    

    When we call this method we must fill the record_ids, which is the id or primary key of the data that will be duplicated. We can also fill the default keyword argument with the value of an associative array. If the default argument is filled, after the data has been duplicated, the new data, the result of the duplication process will be updated according to the value in the default argument. This is an example of how to call the copy method with default value.

    // an example of how to call the copy method
    $partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'copy', [[14]], ['default' => ['street' => 'Jl. Ahamad Yani 14']]);
    
    echo "<pre>" . print_r($partners, true) . "</pre>";
    
  4. Unlink

    This method is used to delete the data in the database. This method has no argument. But we have to fill the record_ids which is the id or primary key of the data which will be deleted.

    $partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'unlink', [[14]]);
    
  5. Search

    This method is used to find some data in the database. This method will return an id wrapped in an array if the search criteria match. This method has several arguments, it is :

    • args

      This argument is called a domain. It is an array where odoo will modify it as a where clause when performing a query in the database.

    • offset and limit

      This argument is used to limit the data that will be displayed. If these two arguments are combined, they can be used to create paging. The data type is an integer.

    • order

      This argument is used to control how the data is sorted. Its data type is a string. Each order clause must be separated by a comma.

    • count

      If set to True it will return the number of search results instead of the model id.

    This is an example of how to call the search method with a domain and some keyword arguments.

    $domain = [
    	['email', 'ilike', 'gmail'],
    	['city', '=', 'Surabaya']
    ];
    $kwargs = ['order' => 'name desc, street asc', 'limit' => 2];
    $partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', [$domain], $kwargs);
    
  6. search_count

    Similar to the search method, this method is used to find the data in the database. But the value that return is the count of the data, not the data itself. In odoo source code this method looks like this.

    @api.model
    def search_count(self, args):
    

    This method only has 1 argument which is a domain. Same as the args arguments in the search method. This is an example of how to use this method.

    $domain = [
    	['email', 'ilike', 'gmail'],
    	['city', '=', 'Surabaya']
    ];
    $partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'search_count', [$domain]);
    
  7. search_read

    Similar to the search method, this method is also used to find the data in the database. But if in the search method the returned data is the id or primary key, in this method we can set up the fields to return. In odoo source code this method looks like this.

    @api.model
    def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
    

    This is an example of how to use this method.

    $domain = [
    	['email', 'ilike', 'gmail'],
    	['city', '=', 'Surabaya']
    ];
    $kwargs = ['order' => 'name desc, street asc', 'domain' => $domain, 'fields' => ['name', 'city', 'street', 'email']];
    $partners = $models->execute_kw($db, $uid, $password, 'res.partner', 'search_read', [], $kwargs);
    

    The result will be like this.

    search_read result in odoo API call

Specific Method

Apart from general methods that can be used in all models, some methods are only available in certain model. This is a specific method. For example, the action_confirm method which is executed when we click on the Confirm button in the sale.order model, like the code below.

def action_confirm(self):
    if self._get_forbidden_state_confirm() & set(self.mapped('state')):
        raise UserError(_(
            'It is not allowed to confirm an order in the following states: %s'
        ) % (', '.join(self._get_forbidden_state_confirm())))

    for order in self.filtered(lambda order: order.partner_id not in order.message_partner_ids):
        order.message_subscribe([order.partner_id.id])
    self.write(self._prepare_confirmation_values())

    # Context key 'default_name' is sometimes propagated up to here.
    # We don't need it and it creates issues in the creation of linked records.
    context = self._context.copy()
    context.pop('default_name', None)

    self.with_context(context)._action_confirm()
    if self.env.user.has_group('sale.group_auto_done_setting'):
        self.action_done()
    return True

The way to call a specific method is no different from a general method.

There are still many methods that can be called via API. Both are the general method that available in all models or specific methods that only available in certain models. Please read the odoo source code, to see all available methods.

Download the source code here.

Related Article

Leave a Reply

Your email address will not be published.