Magento 2 : How to create custom API?

how to create customer API in Magento 2
Today we are going to focus on creating custom web API in Magento 2 using our simple step by step guide. These web APIs allows third party systems like ERP, CRM, POS etc to integrate and pull data directly from Magento 2 as it gives the output in well known formats like REST and SOAP. This also helps in creating your Magento 2 frontend headless because this way you will have all the APIs available to fetch data from Magento 2 so it doesn’t matter whether you use REACT, ANGULAR, KNOCKOUT or any other javascript framework.

Before doing more talking, lets crack on with creating our first custom Magento 2 web API using our step by step approach.

Step 1 – Create webapi.xml under your custom module \Scommerce\Custom\etc\

<route url="/V1/custom/:categoryId/products" method="GET">
    <service class="Scommerce\Custom\Api\CategoryLinkManagementInterface" method="getAssignedProducts" />
    <resources>
        <resource ref="self"/>
    </resources>
</route>

Let us walk you through with the above code to make you understand what’s happening behind the scene -:

– Route – This is the URL which will be used to call our API https://{{MagentoBaseURL}}/index.php/rest/V1/custom/{{categoryId}}/products

– Service Class – This is the interface class of our API and the main method “getAssignedProducts” will be called with {{categoryId}} as the parameter

– Resources- This defines who has the permission to call this API. It could be anonymous (everyone) or self (customer) or specific admin user with specific permission for example Scommerce_Custom::custom which can be added in acl.xml

Step 2 – Lets now create the main interface file for our web API CategoryLinkManagementInterface.php under Scommerce\Custom\Api\ as specified in webapi.xml in Step 1

namespace Scommerce\Custom\Api;

/**
 * @api
 */
interface CategoryLinkManagementInterface
{
    /**
     * Get products assigned to a category
     *
     * @param int $categoryId
     * @return \Scommerce\Custom\Api\Data\CategoryProductLinkInterface[]
     */
    public function getAssignedProducts($categoryId);
}

In the above code, we have created an interface and define the main method i.e. getAssignedProducts as specified in webapi.xml in Step 1. The other thing to notice here is the@return parameter which is the data interface \Scommerce\Custom\Api\Data\CategoryProductLinkInterface[]. Will explain what is data interface in our next step.

Step 3 – Based on the return parameter in Step 2, let’s create our data interface CategoryProductLinkInterface.php under \Scommerce\Custom\Api\Data\

namespace Scommerce\Custom\Api\Data;

/**
 * @api
 */
interface CategoryProductLinkInterface
{
    /**
     * @return string|null
     */
    public function getSku();

    /**
     * @param string $sku
     * @return $this
     */
    public function setSku($sku);

    /**
     * @return string|null
     */
    public function getName();

    /**
     * @param string $name
     * @return $this
     */
    public function setName($name);

    /**
     * @return float|null
     */
    public function getPrice();

    /**
     * @param float $price
     * @return $this
     */
    public function setPrice($price);

    /**
     * @return int|null
     */
    public function getPosition();

    /**
     * @param int $position
     * @return $this
     */
    public function setPosition($position);

    /**
     * @return string|null
     */
    public function getCategoryDescription();

    /**
     * @param string $description
     * @return $this
     */
    public function setCategoryDescription($description);
}

The above data interface class allows you define the response/output of our API request, so as you can see this API will return sku, name, price, position and category description as an output.

Step 4 – Now our interface files are created, let’s create our model classes where we can put the actual business logic, to do so we would need to specify this in our di.xml file under \Scommerce\Custom\etc\

<config ...>
    <preference for="Scommerce\Custom\Api\CategoryLinkManagementInterface" type="Scommerce\Custom\Model\CategoryLinkManagement" />
    <preference for="Scommerce\Custom\Api\Data\CategoryProductLinkInterface" type="Scommerce\Custom\Model\CategoryProductLink" />
</config>

In the above step, we have specified which model classes will be created against our interfaces to add our business logic.

Step 5 – Let’s create our first model class CategoryLinkManagement.php under Scommerce\Custom\Model\ as specified in di.xml

namespace Scommerce\Custom\Model;

/**
 * Class CategoryLinkManagement
 */
class CategoryLinkManagement implements \Scommerce\Custom\Api\CategoryLinkManagementInterface
{
    /**
     * @var \Magento\Catalog\Api\CategoryRepositoryInterface
     */
    protected $categoryRepository;

    /**
     * @var \Scommerce\Custom\Api\Data\CategoryProductLinkInterfaceFactory
     */
    protected $productLinkFactory;

    /**
     * CategoryLinkManagement constructor.
     *
     * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository
     * @param \Scommerce\Custom\Api\Data\CategoryProductLinkInterfaceFactory $productLinkFactory
     */
    public function __construct(
        \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository,
        \Scommerce\Custom\Api\Data\CategoryProductLinkInterfaceFactory $productLinkFactory
    ) {
        $this->categoryRepository = $categoryRepository;
        $this->productLinkFactory = $productLinkFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function getAssignedProducts($categoryId)
    {
        $category = $this->categoryRepository->get($categoryId);
        if (!$category->getIsActive()) {
            return [[
                'error' => true,
                'error_desc' => 'Category is disabled'
            ]];
        }
        $categoryDesc = $category->getDescription();

        /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $products */
        $products = $category->getProductCollection()
            ->addFieldToSelect('position')
            ->addFieldToSelect('name')
            ->addFieldToSelect('price');

        /** @var \Scommerce\Custom\Api\Data\CategoryProductLinkInterface[] $links */
        $links = [];

        /** @var \Magento\Catalog\Model\Product $product */
        foreach ($products->getItems() as $product) {
            /** @var \Scommerce\Custom\Api\Data\CategoryProductLinkInterface $link */
            $link = $this->productLinkFactory->create();
            $link->setSku($product->getSku())
                ->setName($product->getName())
                ->setPrice($product->getFinalPrice())
                ->setPosition($product->getData('cat_index_position'))
                ->setCategoryDescription($categoryDesc);
            $links[] = $link;
        }

        return $links;
    }
}

Step 6 – Lets now create our second model class CategoryProductLink.php under Scommerce\Custom\Model\ as specified in di.xml

namespace Scommerce\Custom\Model;

/**
 * @codeCoverageIgnore
 */
class CategoryProductLink extends \Magento\Framework\Api\AbstractExtensibleObject implements \Scommerce\Custom\Api\Data\CategoryProductLinkInterface
{
    /**#@+
     * Constant for confirmation status
     */
    const KEY_SKU                   = 'sku';
    const KEY_NAME                  = 'name';
    const KEY_PRICE                 = 'price';
    const KEY_CATEGORY_DESC         = 'category_description';
    const KEY_POSITION              = 'position';
    /**#@-*/

    /**
     * {@inheritdoc}
     */
    public function getSku()
    {
        return $this->_get(self::KEY_SKU);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return $this->_get(self::KEY_NAME);
    }

    /**
     * {@inheritdoc}
     */
    public function getPosition()
    {
        return $this->_get(self::KEY_POSITION);
    }

    /**
     * {@inheritdoc}
     */
    public function getPrice()
    {
        return $this->_get(self::KEY_PRICE);
    }

    /**
     * {@inheritdoc}
     */
    public function getCategoryDescription()
    {
        return $this->_get(self::KEY_CATEGORY_DESC);
    }

    /**
     * @param string $sku
     * @return $this
     */
    public function setSku($sku)
    {
        return $this->setData(self::KEY_SKU, $sku);
    }

    /**
     * @param string $name
     * @return $this
     */
    public function setName($name)
    {
        return $this->setData(self::KEY_NAME, $name);
    }

    /**
     * @param int $position
     * @return $this
     */
    public function setPosition($position)
    {
        return $this->setData(self::KEY_POSITION, $position);
    }

    /**
     * @param float $price
     * @return $this
     */
    public function setPrice($price)
    {
        return $this->setData(self::KEY_PRICE, $price);
    }

    /**
     * @param string $description
     * @return $this
     */
    public function setCategoryDescription($description)
    {
        return $this->setData(self::KEY_CATEGORY_DESC, $description);
    }

}

The above will allow us to call our first Magento 2 web API using the customer token. To retrieve admin or custom token, please have a look at Magento official token retrieval documentation

That’s it, Hope this article helped you in some way. Please leave us your comment and let us know what do you think? Thanks.

13 thoughts on “Magento 2 : How to create custom API?

    1. Good question Serg, you can test them using postman (chrome extension).

      1) Get admin token using POST https://{{SITEURL}}/index.php/rest/V1/integration/admin/token

      • Header – KEY: Content-Type, VALUE: application/json
      • Body – {“username”:”username”,”password”:”password”}

      The above will return admin token as response, which should look like “bjf857c6jf17vxpn9av9sqccej1s998e”

      2) Call Category API using GET https://{{SITEURL}}/index.php/rest/V1/category using header –

      • Header – KEY: Authorization and VALUE: bjf857c6jf17vxpn9av9sqccej1s998e

      Hope this helps

        1. Please download postmaster on chrome and it is quite self explanatory to use.

          Once downloaded then do the following

          1) Get admin token using POST https://{{SITEURL}}/index.php/rest/V1/integration/admin/token

          Header – KEY: Content-Type, VALUE: application/json
          Body – {“username”:”username”,”password”:”password”}
          The above will return admin token as response, which should look like “bjf857c6jf17vxpn9av9sqccej1s998e”

          2) Call Category API using GET https://{{SITEURL}}/index.php/rest/V1/category using header –

          Header – KEY: Authorization and VALUE: Bearer bjf857c6jf17vxpn9av9sqccej1s998e

  1. Getting error when run you code

    Fatal error: Uncaught Error: Call to undefined method Royal\Customapi\Model\CategoryProductLink: :setData() in D:\xampp\htdocs\magento2\app\code\Royal\Customapi\Model\CategoryProductLink.php: 35
    Stack trace:
    #0 D:\xampp\htdocs\magento2\app\code\Royal\Customapi\Model\CategoryLinkManagement.php(58): Royal\Customapi\Model\CategoryProductLink->setSku(‘test123’)
    #1 [internal function
    ]: Royal\Customapi\Model\CategoryLinkManagement->getAssignedProducts(2)
    #2 D:\xampp\htdocs\magento2\vendor\magento\module-webapi\Controller\Rest\SynchronousRequestProcessor.php(95): call_user_func_array(Array, Array)
    #3 D:\xampp\htdocs\magento2\vendor\magento\module-webapi\Controller\Rest.php(188): Magento\Webapi\Controller\Rest\SynchronousRequestProcessor->process(Object(Magento\Framework\Webapi\Rest\Request\Proxy))
    #4 D:\xampp\htdocs\magento2\vendor\magento\framework\Interception\Interceptor.php(58): Magento\Webapi\Controller\Rest->dispatch(Object(Magento\Framework\App\Request\Http))
    #5 D:\xampp\htdocs\magento2\vendor\magento\framework\Interception in D:\xampp\htdocs\magento2\app\code\Royal\Customapi\Model\CategoryProductLink.php on line 35
    {
    “messages”: {
    “error”: [
    {
    “code”: 500,
    “message”: “Server internal error. See details in report api/1288974249951”
    }
    ]
    }
    }

    1. Hi Sangeeta, can you please confirm that your CategoryProductLink class is using implement as shown below?

      implements \Scommerce\Custom\Api\Data\CategoryProductLinkInterface

      Also your CategoryLinkManagement class is using implement as as below

      implements \Scommerce\Custom\Api\CategoryLinkManagementInterface

  2. Amazingly Great job. These two points are well covered; “Step #2” and “Step #5”. Thanks for sharing this topic “MAGENTO 2 : HOW TO CREATE CUSTOM API?”. The best part is the article has all the practical detailing! Keep sharing

    1. Hi there, you get this error if you don’t have matching entry in webapi.xml file. Please double check the entry and also make sure you run setup:upgrade and setup:di:compile. Hope it helps!

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.