in Magento, Programming, Web Development

Extending magento’s REST Api v2 – Tutorial

In this post I’m going to go through all the steps you need to make in order to extend the REST Api, there is very little documentation scattered around on this and I had to pull pieces together from various official sources, not only that but the responses Magento gives you can at times be confusing and does not really give you a hint to what the problem is, finally this is a full guide so no knowledge is assumed other than setting up the rest roles/users and assuming you have ssh access on the server.

This guide is split into two parts. Part 1 configuring REST access and Oauth setup, Part 2 extending the Api if you already have the api access and oauth configured correctly then skip to part 2.

Part 1 – configuring REST access and Oauth setup

Firstly set up your roles/users follow the guide on the Magento site

  • Add a new user with an admin role
  • Add all resources to the role in Role Api Resources
  • In Rest attributes for the admin role select All resources
  • Add a new consumer

Install/Configure Oath

it’s likely you won’t have this set up on your server, this needs to be installed via PECL, if you do want to check that you don’t already have it, check phpinfo(); and see if you have these lines:

oauth

It’s important here to have curl in the Request engine support, if you don’t this means you need to install curl (apt-get install php5-curl) or oauth has not been installed correctly. If you do have it you can try upgrading

pecl upgrade oauth

or you may have to uninstall/reinstall if for example you didn’t have curl before installing oauth.

pecl uninstall oauth

There is another caveat with 1.2.3 of oauth and that is Magento uses the multi update HTTP_MULTI_STATUS with a 207 response code when you issue PUT requests to update multiple items. In this version of oauth it incorrectly gives Invalid/bad auth response when using this response code, to get around this you can compile your own version with a fix (the bug has been fixed in the latest SVN release).

If your not bothered about using the multi update via PUT then go ahead and install oauth

pecl install oauth 

To compile your own version download it from the PECL site http://pecl.php.net/package/oauth

Replace oauth.c with the fixed version

Upload all the files into a directory, if you haven’t already then issue the following commands a line at a time: (replace extname with the directory your working with)

$ cd extname
$ phpize
$ ./configure
$ make
$ make install

This should now have installed the extension correctly, you can check by doing:

pecl info oauth

And by looking at the phpinfo(), ok so now oauth is configured correctly we can run some tests.

Create a directory in the root of your site, e.g /tests and create a file called customers.php place the following code below changing:

  • mydomain.com
  • CONSUMEY_KEY
  • CONSUMER_SECRET

You can get the key and secret from System > Web Services > Oauth Consumers > Select the appropriate key you set up earlier.


<?php

require '../app/Mage.php'; //Path to Magento
Mage::app();

// $callbackUrl is a path to your file with OAuth authentication example for the Admin user
$callbackUrl = "http://mydomain.com/tests/customers.php";
$temporaryCredentialsRequestUrl = "http://mydomain.com/oauth/initiate?oauth_callback=" . urlencode($callbackUrl);
$adminAuthorizationUrl = 'http://mydomain.com/admin/oauth_authorize';
$accessTokenRequestUrl = 'http://mydomain.com/oauth/token';
$apiUrl = 'http://mydomain.com/api/rest';
$consumerKey = 'CONSUMER_KEY';
$consumerSecret = 'CONSUMER_SECRET';

session_start();
if (!isset($_GET['oauth_token']) && isset($_SESSION['state']) && $_SESSION['state'] == 1) {
 $_SESSION['state'] = 0;
}
try {
 $authType = ($_SESSION['state'] == 2) ? OAUTH_AUTH_TYPE_AUTHORIZATION : OAUTH_AUTH_TYPE_URI;
 $oauthClient = new OAuth($consumerKey, $consumerSecret, OAUTH_SIG_METHOD_HMACSHA1, $authType);
 $oauthClient->enableDebug();

if (!isset($_GET['oauth_token']) && !$_SESSION['state']) {
 $requestToken = $oauthClient->getRequestToken($temporaryCredentialsRequestUrl);
 $_SESSION['secret'] = $requestToken['oauth_token_secret'];
 $_SESSION['state'] = 1;
 header('Location: ' . $adminAuthorizationUrl . '?oauth_token=' . $requestToken['oauth_token']);
 exit;
 } else if ($_SESSION['state'] == 1) {
 $oauthClient->setToken($_GET['oauth_token'], $_SESSION['secret']);
 $accessToken = $oauthClient->getAccessToken($accessTokenRequestUrl);
 $_SESSION['state'] = 2;
 $_SESSION['token'] = $accessToken['oauth_token'];
 $_SESSION['secret'] = $accessToken['oauth_token_secret'];
 header('Location: ' . $callbackUrl);
 exit;
 } else {
 $oauthClient->setToken($_SESSION['token'], $_SESSION['secret']);

$resourceUrl = "$apiUrl/customers";
 $oauthClient->fetch($resourceUrl, array(), 'GET', array('Content-Type' => 'application/json'));
// print_r($_SESSION);
// die();
 echo $oauthClient->getLastResponse();
 }
} catch (OAuthException $e) {
 print_r($e->getMessage());
 echo "<br/>";
 print_r($e->lastResponse);
}

This should first ask you to authorise , proceed and accept the request and if all goes well you should now have a list of customers in JSON format, if you don’t want JSON then replace the fetch with


$oauthClient->fetch($resourceUrl, array(), 'GET', array('Accept' => 'text/xml'));

If you’ve got this far your now ready to extend the API, If you get any other response check that you have assigned the role properly and everywhere there is a resource dropdown it’s set to ALL. I’m not going to cover how to do this in a browser as well as Magento provides a guide on this , follow the guide you can get the access token/secret by uncommenting the two lines in the above script:


// print_r($_SESSION);
// die();

Part 2 extending the Api

Assuming you’ve set up the access and oauth correctly, (note incorrect oauth set up may look like it’s working when it isn’t!).

Let’s create a custom call for managing customer groups, create the following structure:

app/code/local/Custom
app/code/local/Custom/Restapi
app/code/local/Custom/Restapi/Groups
app/code/local/Custom/Restapi/Groups/Model
app/code/local/Custom/Restapi/Groups/Model/Api2
app/code/local/Custom/Restapi/Groups/Model/Api2/Group
app/code/local/Custom/Restapi/Groups/Model/Api2/Group/Rest
app/code/local/Custom/Restapi/Groups/Model/Api2/Group/Rest/Admin
app/code/local/Custom/Restapi/Groups/Model/Api2/Group/Rest/Admin/V1.php
app/code/local/Custom/Restapi/Groups/etc
app/code/local/Custom/Restapi/Groups/etc/api2.xml
app/code/local/Custom/Restapi/Groups/etc/config.xml

Enable our extension, create the following file

app/etc/modules/Custom_Restapi_Groups.xml
<config>
    <modules>
        <Custom_Restapi_Groups>
            <active>true</active>
            <codePool>local</codePool>
        </Custom_Restapi_Groups>
    </modules>
</config>

Set up the config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Custom_Restapi_Groups>
            <version>0.1.0.0</version>
        </Custom_Restapi_Groups>
    </modules>
    <global>
        <models>
           <groups>
                <class>Custom_Restapi_Groups_Model</class>
            </groups>
        </models>

    </global>
</config>

set up the api2.xml

<?xml version="1.0"?>
<config>
    <api2>
        <resource_groups>
            <catalog translate="title" module="api2">
                <title>Catalog</title>
                <sort_order>10</sort_order>
            </catalog>
        </resource_groups>
        <resources>
            <groups translate="title" module="api2">
                <group>catalog</group>
                <model>groups/api2_group</model>
                <title>Groups</title>
                <sort_order>10</sort_order>
                <privileges>
                    <admin>
                        <retrieve>1</retrieve>
                        <create>1</create>
                              <update>1</update>
                              <delete>1</delete>
                    </admin>

                </privileges>
                <attributes>

                    <name>Name</name>
                </attributes>

                <routes>
                    <route_entity>
                        <route>/groups/group/:id</route>
                        <action_type>entity</action_type>
                    </route_entity>
                    <route_collection>
                        <route>/groups/</route>
                        <action_type>collection</action_type>
                    </route_collection>

                </routes>

                <versions>1</versions>
            </groups>
        </resources>
    </api2>
</config>

Here the resource groups refer to which group it appears in the backend role configuration in the admin, it’s title and sort order and the privileges available, The attributes section is important for when your using POST if you do not specify the variables your posting you will get an error 400 bad request.

Finally we define our routes another important note here is when using PUT/POST you need to use the action_type collection as certain methods are only available to the collection type, if you unsure or trying to replicate how the default api works have a look in the api2.xml of the module for example this is the same as /app/code/core/Mage/Customer/etc/api2.xml

 <routes>
                    <route_entity>
                        <route>/customers/:id</route>
                        <action_type>entity</action_type>
                    </route_entity>
                    <route_collection>
                        <route>/customers</route>
                        <action_type>collection</action_type>
                    </route_collection>
                </routes>

Now that’s set up lets populate our model in V1.php

<?php
class Custom_Restapi_Groups_Model_Api2_Group_Rest_Admin_V1 extends Mage_Api2_Model_Resource
{

    /**
     * Create a customer group
     * @return array
     */

    public function _create() {
        //Create Customer Group
        $requestData = $this->getRequest()->getBodyParams();
        $groupName = $requestData['name'];
        Mage::getSingleton('customer/group')->setData(
            array('customer_group_code' => $groupName,'tax_class_id' => 3))
            ->save();

        $targetGroup = Mage::getSingleton('customer/group');
        $groupId = $targetGroup->load($groupName, 'customer_group_code')->getId();

        if($groupId) {
            $json = array('id' => $groupId);
            echo json_encode($json);
            exit();
        }

    }

     /**
     * Retrieve a group name by ID
     * @return string
     */

    public function _retrieve()
    {
        //retrieve a group name by ID
        $customerGroupId = $this->getRequest()->getParam('id');
        $groupname = Mage::getModel('customer/group')->load($customerGroupId)->getCustomerGroupCode();

        return $groupname;

    }

}
?>

Now we have our extension completed let’s try it out in a test, create the file groupInsert.php in the tests folder as before replace mydomain and the keys..

<?php

require '../app/Mage.php'; //Path to Magento
Mage::app();

// $callbackUrl is a path to your file with OAuth authentication example for the Admin user
$callbackUrl = "http://mydomain.com/tests/index.php";
$temporaryCredentialsRequestUrl = "http://mydomain.com/oauth/initiate?oauth_callback=" . urlencode($callbackUrl);
$adminAuthorizationUrl = 'http://mydomain.com/admin/oauth_authorize';
$accessTokenRequestUrl = 'http://mydomain.com/oauth/token';
$apiUrl = 'http://mydomain.com/api/rest';
$consumerKey = 'CONSUMER_KEY';
$consumerSecret = 'CONSUMER_SECRET';
session_start();
if (!isset($_GET['oauth_token']) && isset($_SESSION['state']) && $_SESSION['state'] == 1) {
    $_SESSION['state'] = 0;
}
try {
    $authType = ($_SESSION['state'] == 2) ? OAUTH_AUTH_TYPE_AUTHORIZATION : OAUTH_AUTH_TYPE_URI;
    $oauthClient = new OAuth($consumerKey, $consumerSecret, OAUTH_SIG_METHOD_HMACSHA1, $authType);
    $oauthClient->enableDebug();

    if (!isset($_GET['oauth_token']) && !$_SESSION['state']) {
        $requestToken = $oauthClient->getRequestToken($temporaryCredentialsRequestUrl);
        $_SESSION['secret'] = $requestToken['oauth_token_secret'];
        $_SESSION['state'] = 1;
        header('Location: ' . $adminAuthorizationUrl . '?oauth_token=' . $requestToken['oauth_token']);
        exit;
    } else if ($_SESSION['state'] == 1) {
        $oauthClient->setToken($_GET['oauth_token'], $_SESSION['secret']);
        $accessToken = $oauthClient->getAccessToken($accessTokenRequestUrl);
        $_SESSION['state'] = 2;
        $_SESSION['token'] = $accessToken['oauth_token'];
        $_SESSION['secret'] = $accessToken['oauth_token_secret'];
        header('Location: ' . $callbackUrl);
        exit;
    } else {
        $oauthClient->setToken($_SESSION['token'], $_SESSION['secret']);
        $resourceUrl = "$apiUrl/groups/";
        $productData = Mage::helper('core')->jsonEncode(array(
            'name'      => 'test_group'
        ));
        $headers = array('Content-Type' => 'application/json');
        $oauthClient->fetch($resourceUrl, $productData, 'POST', array('Content-Type' => 'application/json'));
        echo $oauthClient->getLastResponse();
    }
} catch (OAuthException $e) {
    print_r($e);
}

Run this file in your browser and you should get a JSON response with the name and ID, likewise we can get the group ID from a name by replacing $resourceURL and $oauthClient with the following:

 $resourceUrl = "$apiUrl/groups/group/1";
 $oauthClient->fetch($resourceUrl, array(), 'GET', array('Content-Type' => 'application/json'));

That’s it, you’ve now extended the API! If you want to use the other methods for example to GET all groups you will need to write the various functions, check your exception log to see what function is required as you will get an error similar to:

#0 /var/www/website/app/code/core/Mage/Api2/Model/Resource.php(301): Mage_Api2_Model_Resource->_critical('Resource method...')
#1 /var/www/website/app/code/core/Mage/Api2/Model/Resource.php(251): Mage_Api2_Model_Resource->_errorIfMethodNotExist('_update')
#2 /var/www/website/app/code/core/Mage/Api2/Model/Dispatcher.php(74): Mage_Api2_Model_Resource->dispatch()

Then simply add this to your class , monitor the exception log for other errors too, sometimes I would receive the error

exception 'Mage_Api2_Exception' with message 'oauth_problem=token_rejected' in /var/www/website/app/code/core/Mage/Api2/Model/Auth/Adapter/Oauth.php:61

When I didn’t have the correct attribute set up or something else was wrong event though the token was valid, the other errors your likely to encounter

exception 'Mage_Api2_Exception' with message 'Decoding error.'

The input here is wrong check your JSON/XML formatting

exception 'Mage_Api2_Exception' with message 'The request data is invalid.'

Did you add the correct attribute?

exception 'Mage_Api2_Exception' with message 'Resource data pre-validation error.'

The data is not in the expected format, for example if your trying to populate a multi-select attribute this needs to be in an array with the correct ID’s

Please do get in touch if you have anything to add to my example or i’ve missed something or alternatively if you would like your own API developed or help writing one.

Write a Comment

Comment

28 Comments

    • 404 probably means the module is not loaded correctly as your routes have not been loaded, check the naming of your class definitions and file structure. Also check in api2.xml

        
                      <routes>
                          <route_entity>
                              <route>/custom/</route>
                              <action_type>entity</action_type>
                          </route_entity>
                      </routes>
      

      That you are calling the route with or without the / at the end however you defined it.

  1. Great article,

    I like how you pay attention to details, such as needs for curl in Request Engine Support and great explanations!

    Best regards,
    Razvan

  2. I do all you write in the article but i have a problem. the method GET work fine but the POST method return always:

    [body_recv] => {“messages”:{“error”:[{“code”:400,”message”:”The request data is invalid.”}]}}

    i try to track the error and i found that error are here:

    if ($this->getRequest()->isAssocArrayInRequestBody()) {
    $this->_errorIfMethodNotExist(‘_create’);
    $filteredData = $this->getFilter()->in($requestData);
    if (empty($filteredData)) {
    $this->_critical(self::RESOURCE_REQUEST_DATA_INVALID);
    }
    $newItemLocation = $this->_create($filteredData);
    $this->getResponse()->setHeader(‘Location’, $newItemLocation);
    }

    the array: filteredData return empty in your example. How is possible to fix it ?

  3. In my case also post method giving “Invalid Content-Type header”
    for debugging purpose I put simple create but still it’s giving same error. My simple create function below.
    public function _create() {
    return json_encode(array(“Test”,”Success”));
    }

  4. Great article.

    I have followed exactly like your tutorial and am getting an error called “decoding error”. Kindly help to fix on this.

  5. For those of you having error 404:
    Here is a possible solution. It worked for me:
    Change
    $apiUrl = ‘http://mydomain.com/api/rest’;
    to
    $apiUrl = ‘http://mydomain.com/api.php?type=rest’;

    make sure to replace mydomain.com by your own domain name.

    I hope this helps.

  6. Hello,

    I have to try to use post method with custome REST api custome model call but get error

    {“messages”:{“error”:[{“code”:400,”message”:”The request data is invalid.”}]}}

      • How to set delete,create & update in api2.xml , like routes & action_type. I saw dispatch functionality in app/code/core/Mage/Api2/Model/Resource.php for route actions.

  7. Hi, i create a custom rest api like the above method, but its returns OAuthException Object error Like Invalid auth/bad request (got a 500, expected HTTP/1.1 20X or a redirect . My groupInsert file path is Projects/mydomain/public/api-test/groupInsert.php & the url are given below,
    $callbackUrl = “http://mydomain.com/api-test/groupInsert.php”;
    $temporaryCredentialsRequestUrl = “http://mydomain.com/oauth/initiate?oauth_callback=” . urlencode($callbackUrl);
    $adminAuthorizationUrl = ‘http://mydomain.com/admin/oauth_authorize’;
    $accessTokenRequestUrl = ‘http://mydomain.com/oauth/token’;
    $apiUrl = ‘http://mydomain.com/api/rest’;

    The Detail Error:
    OAuthException Object ( [message:protected] => Invalid auth/bad request (got a 500, expected HTTP/1.1 20X or a redirect)
    [string:Exception:private] => [code:protected] => 500 [file:protected] => /home/reniraj/Documents/Projects/mydomain/public/api-test/groupInsert.php
    [line:protected] => 44 [trace:Exception:private] => Array ( [0] => Array ( [file] => /home/reniraj/Documents/Projects/mydomain/public/api-test/groupInsert.php [line] => 44 [function] => fetch [class] => OAuth [type] => -> [args] => Array ( [0] => http://mydomain.com/api/rest/groups/ [1] => {“name”:”api_test_group”} [2] => POST [3] => Array ( [Content-Type] => application/json [Authorization] => OAuth oauth_consumer_key=”xxxxxxxx”,oauth_signature_method=”HMAC-SHA1″,oauth_nonce=”xxxxxxxx”,oauth_timestamp=”1456140957″,oauth_version=”1.0″,oauth_token=”xxxxxxxxx”,oauth_signature=”xxxxxxx” ) ) ) ) [previous:Exception:private] => [lastResponse] => Service temporary unavailable [debugInfo] => Array ( [sbs] => POST&http%3A%2F%2Fmydomain.com%2Fapi%2Frest%2Fgroups%2F&oauth_consumer_key%3Dxxxxxxxx%26oauth_nonce%3Dxxxxxxxx%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1456140957%26oauth_token%3Dxxxxxxxxc%26oauth_version%3D1.0 [headers_sent] => Content-Type: application/json Authorization: OAuth oauth_consumer_key=”xxxxxxx”,oauth_signature_method=”HMAC-SHA1″,oauth_nonce=”xxxxxxx”,oauth_timestamp=”1456140957″,oauth_version=”1.0″,oauth_token=”xxxxxxxxx”,oauth_signature=”xxxxxxxD” [headers_recv] => HTTP/1.1 500 Internal Server Error Date: Mon, 18 Feb 2016 11:35:57 GMT Server: Apache/2.4.7 (Ubuntu) X-Powered-By: PHP/5.5.9-1ubuntu4.14 Content-Length: 29 Connection: close Content-Type: text/html [body_sent] => {“name”:”api_test_group”} [body_recv] => Service temporary unavailable ) )

  8. First verify if your group insert is working, try running the script outside of the Oauth call, if your still having trouble look at your server logs and magento’s logs to see what’s causing the 500 response.

  9. Hi guys,
    I encounter the same problem (error 400) when posting request in JSON format (same instructions in XML is working fine, means is not attribute related).

    {“messages”:{“error”:[{“code”:400,”message”:”The request data is invalid.”}]}}

    Jonathan & Milan, guys did you found a solution to figure it out?

    • Are you adding ‘Content-Type’ => ‘application/json’ to your request? which method are you calling?

      • Finally, I’ve found the problem.

        My request in XML contains the following first node:

        In JSON, I was also starting my request with:
        {“magento_api”: {

        Problem was: with JSON, first “magento_api” node must be removed.

        After the correction, answer/request is working like a charm.

  10. Yes I add this header in my request.

    I’m calling the following method by POST: api/rest/mysite/orders

    Note: I’m able to receive successfully results by GET from api/rest/mysite/orders (read orders) with both JSON and XML format.

  11. I tried to authorize it via android app but when I get resources (api/rest/products), I got error:

    {“messages”:{“error”:[{“code”:401,”message”:”oauth_problem=internal_error&message=Mage registry key \”_singleton\/admin\/session\” already exists”}]}}

    I have no idea.

    • I’ve had that error before but only if your calling getSingleton core/session somewhere else first. Maybe your trying to authenticate twice or something?

  12. Hi Joel,

    Thanks a lot for this blog.

    I am trying to do the same as this article but with MAGENTO 2.0.

    I am lost.

    Could you update this post with Magento 2.0 version?

    Regards,

    • I haven’t tried this, It may be possible because the controller looks for the _method() but I believe the basic CRUD should be enough, in your case it would be better setting this up as another route entity e.g /forgot/:id

Webmentions

  • Custom Rest Api in Magento – Magento2 February 8, 2017

    […] I need rest api to create customer in magneto for that I followed this tutorial http://www.authenticdesign.co.uk/extending-magento-rest-api-v2/ […]

  • Get Associated Products listed in Configurable Product Array from REST API - gomagento2 February 8, 2017

    […] http://www.authenticdesign.co.uk/extending-magento-rest-api-v2/ […]

  • php - Custom Rest Api in Magento - php February 8, 2017

    […] i need rest api create client in magneto followed tutorial http://www.authenticdesign.co.uk/extending-magento-rest-api-v2/ […]

  • Custom Rest Api in Magento - Tech Magazine February 8, 2017

    […] I need rest api to create customer in magneto for that I followed this tutorial http://www.authenticdesign.co.uk/extending-magento-rest-api-v2/ […]

  • Custom Rest Api in Magento - DexPage February 8, 2017

    […] I need rest api to create customer in magneto for that I followed this tutorial http://www.authenticdesign.co.uk/extending-magento-rest-api-v2/ […]