Creating a New REST Web Service in Magento 2

In this post I work through a working example of how to create a new REST web service in Magento 2. My goal is to show how easy it is define a new RESTful service. The examples start with a simple integers, moves on to arrays, then ends with a more complex data type to pass into and return from a service call. This post focusses on JSON, but REST with XML and SOAP are also supported by Magento 2. Full documentation can be found on http://devdocs.magento.com under the “Web Services Developer”.

To keep the following examples as simple as possible, the new APIs perform some mathematical operations on input parameters. There are no database tables for storage, and no authentication. (Magento 2 supports three forms of authentication, as described in the full documentation on the site. The three forms are suitable for AJAX calls from the user’s web session, from a mobile device, and from an external application where an authentication token must be stored in a file on disk.)

Example 1: Add 2 Numbers

The first example is to add two integers, where the two arguments are passed on the URL. This illustrates how to extract parameters from the URL, and shows a very simple function prototype. I will show all the files first, then explain some of the less obvious points. Some parts are omitted as they will be introduced below in later steps.

I used the vendor name “AlanKent” for my module, with a module name of “CalculatorWebService”.

The above is the complete definition of a new module exposing a new REST web service to add two numbers. Briefly the files have the following purposes.

etc/module.xml – declares the module, it’s name, it’s dependencies, and it’s DB schema version (currently needed even if there is no DB).

etc/di.xml – declares that any request for the calculator service contract interface can be satisfied by instantiating the calculator class. (Other modules could swap in a new implementation by overriding this preference.

etc/webapi.xml – binds a URL with two parameters as path segments to the add() method of the calculator interface. “Resources” is the Magento terminology for access control lists – “anonymous” means anyone can access the service.

Api/CalculatorInterface.php – declares a PHP interface with an add(num1, num2) method. Note that the PHP doc for the method is very important, as it is used to determine the types of the function arguments and return value.

The Magento framework will then parse the URL, extract the arguments from the URL, and provide as arguments to the add() function call. Here is a sample of running the service using CURL.

$ curl http://127.0.0.1/index.php/rest/V1/calculator/add/1/2
3

The return value will be “3”, rather than a full JSON object. This is because the return value is a simple type (an integer).

Note there is no code required to serialize or deserialize JSON – that is done by the Magento framework. The same code will work with SOAP by changing the etc/webapi.xml file.

And that is it. A complete definition of a module that defines a web service for other applications to access in 5 fairly short files.

Example 2: Sum an Array of Floating-point Numbers

Let’s now add a new method where we POST a JSON object holding an array of values to be added up, with the total sum returned. This is fairly straightforward, as Magento knows also how to encode/decode arrays and objects as well. The additional lines are highlighted in bold.

As can be seen, you simply add a new method to the interface and class implementing the interface, then add a new route in the webapi.xml file to bind a URL to the new sum() method. This new service can be invoked as follows:

Instead of passing parameters on the URL, this example sends a JSON object with a HTTP POST to the specified URL. The arguments are encoded in a JSON object, with a field per function call parameter. In this case, the function sum() is declared with a single parameter “nums” (see the interface definition) which is an array of floating point numbers. The framework again does all the JSON parsing of input data and encoding of the return value, handling a range of simple types..

Example 3: Mid-point Between two Points

As an example of passing in and returning a more complex JSON object, consider a function midPoint() that takes two Point instances (holding x and y coordinate values) and returns a new Point instance that represents the mid-point between the two coordinates. Again, I will show the code first with changes from example 2 in bold.

The -d option causes CURL to do a POST of the provided JSON object (two arguments called point1 and point2). The returned object is also a Point instance with x and y coordinates. Running quickly through the changes again,

etc/di.xml – adds the class reference for Points.

etc/webapi.xml – adds the new URL to function call binding.

Api/CalculatorInterface.php – adds the new mid-point function.

Api/Data/PointInterface.php – an important new file, acting as the definition of the encoding and decoding of JSON objects. This class is in the Api/Data directory to identify it as an interface for holding a data structure for function call arguments and/or return values.

Module/Calculator.php – has the new method added, but also now has a constructor that takes a ‘PointInterfaceFactory’ argument. I have not supplied this file as the Magento framework will automatically create the class for us. It will have a single create() method that returns an instance of the requested Point instance, but calling the Magento object manager (which will examine the di.xml file to work out the right class to create for PointInterface).

This post does not try to explain every concept with writing a web service – please see the online documentation http://devdocs.magento.com/ for that. The goal of this was to provide a few simple examples of writing your own web service, to demonstrate how easy it is to define a new set of functions and expose them as REST (or SOAP) web services. I did not cover how to use SOAP instead of REST+JSON, and I also did not cover how authentication works. Please consult the manuals for this additional information.

Service contracts do not support all types. You cannot use array() because we need to know what it is an array of, and associative array currently is not supported at all (we only support data interfaces which is serialized as a JSON object with name/value pairs.)

To expand, you cannot say “@return array”. You need to do something like “@return CustomerInterface[]”. You need to be explicit about the type of values in the array. Also we do not support associateArray” directly. You have to declare a data interface (one with setters/getters). Each setter/getter turns into a JSON object field (which is like keys of an associate array).

Thanks Alan. So now I have to declare a \Api\Data\Property2Interface (with a \Model\Data\Property2.php and a new record in the di.xml). Inside \Api\Data\Property2Interface I have to define a setter/getter “property” with type \Api\Data\Property2Interface[] and the in my json I will have to write something like:

If you want property 2 to be an array of strings, you declare it as string[]. If you want an array of JSON objects with a member “property” set to an array of integers then you need a new data interface. That is

Hi Alan,
I have installed fresh magento2 in my windows system. I have got the following error while accessing the WSDL file by soap method(http://127.0.0.1/magento2/soap/default?wsdl=1&services=catalogProductRepositoryV1)
Class “array[]” does not exist. Please note that namespace must be specified.
#0 D:\xampp\htdocs\magento2\app\code\Magento\Webapi\Model\Soap\Config\ClassReflector.php(92): Magento\Framework\Reflection\TypeProcessor->register(‘array[]’)

I’m having a hard time getting the first part of the demo to work (up to the 1+2 = 3 part). I copied and pasted all of your examples into my clean Magento2 install and I keep getting this error:

Request does not match any route.

I have magento running over https, and I can get to the admin site and the public site. It is running over a self-signed cert, but it’s just my dev box at this point and I installed the cert so my browser would quit complaining. Both in the browser and in curl (with -k to turn off the cert checks) I get the same error as stated above.

but the solution is written for Magento1, and doesn’t work in Magento2 apparently. Can you point me to some documentation for Magento2 that would show how to programmatically create a store? I noticed the API doesn’t have store creation functionality (or I haven’t found it at least), so I need to extend the API to allow me to create stores.

OK, I figured it out by looking at the code. I still think better documentation is in order, but I can contribute to it here I guess. Here’s what worked for me:
objectManager = ObjectManager::getInstance();
}

Note that this makes the website, storegroup, and store all with the same code. I haven’t figured out yet if that’s OK, but it shows up in the admin interface as having been created, so it looks like it works. 🙂

The idea is to declare the factories as an argument to the constructor and save them away in properties. Then you call $this->whateverFactory->create(); to create a new instance. Your code ideally should never mention objectFactory directly. If you are using ‘new’ directly, that is also a hint of a possible issue. (Sometimes ‘new’ is OK, but generally for Magento classes you would use a factory to create them.)

Not all APIs are available by service contracts yet – something we will improve over subsequent releases. In the mean time, you can declare your own APIs if you need to expose something via REST etc.

And yes, registration.php was a later addition, as part of the support to allow modules to exist under the ‘vendor’ directory. We have a bit more work still to do, in order to allow modules to exist outside the root directory of the Magento installation – this would give developers a bit more flexibility when developing custom modules. But no ETA on when we will get to this – but it is on the backlog. Again, the registration script is how we will work out the location of the module in such cases.

I’m working to integrate Magento into my ZF2 application via the API, as you suggested in a previous response to one of my comments above. I have successfully added custom API calls to create a new website, store, and root category as well as to delete them all. Now I’m working on creating a new simple product (also a custom API call, as I saw somewhere that the SimpleProduct creation is not currently supported by the API…please correct if I’m wrong).

I’m getting a weird error. I’m trying to pass in an array of data to the API call and it’s choking on the array being passed in. I’m getting this error:
main.CRITICAL: exception ‘ReflectionException’ with message ‘Report ID: webapi-5671a20727a08; Message: Class array does not exist’ in /usr/local/zend/var/apps/http/magento-dev.mydomain.com/80/_docroot_/vendor/magento/framework/Webapi/ErrorProcessor.php:194

I have tried @param string[] as well and got different errors, still no luck. I tried switching it to a GET in webapi.xml instead of a POST, and I can get it to work. I think the problem is specifically with the @param array part. It keeps choking on that. Please advise.

OK, I figured this one out, too. The $data array I was passing in was almost all strings, but one of them was an array of strings. It was causing the built-in magento TypeProcessor to crash here:
Message: Notice: Array to string conversion in /usr/local/zend/var/apps/http/magento-dev.mydomain.com/80/_docroot_/vendor/magento/framework/Reflection/TypeProcessor.php on line 496′

I worked around it by serializing the data in that element and then unserializing it after it was POSTed to Magento. Is this something that could be fixed so that Magento handles it more gracefully? I would think that Magento should be able to handle complex data types being posted via JSON. Thoughts?

We do not support all possible json data types. We support array of one type, object (name value pairs where each name has a defined type). We do not support array of mixed types as it does not map well to WSDL and other programming languages. There purposely is a restricted set of types we support. We want to be compatible with multiple languages, multiple encodings, etc. json is only one such encoding. Does that make sense?

Sorry, just found this in my blog spam folder. Try adding the HTTP header “Accept: application/json” to your request in order to request the response in JSON. Yes, the default is XML encoded otherwise.

Thanks – my example is simpler than what you are asking. You can extend existing web service APIs to include additional information in the response (for example), but I don’t have an example of this on my blog at this stage. In Magento 2, product types are loaded by different modules (so you can remove “bundled products” from the site for example). Some product types extend the base web API for returning product information. If the desired information is not included in the response, you may be able to extend an existing API using this sort of approach.

Hi Alan!,
With the help of your article, I have created several REST API for our current requirement in Magento 2.0 based project,Now I am stuck with a requirement to manage user and roles for the multiple store/website. I want to create a user for a particular role/resources for particular store/website and want to manage by REST API, Please suggest it is possible in magento2.0 or not?

Hi Alan,
With the help of your REST API example, I have created several modules in magento2.0 now, I want to create a plugin in magento2.0 which helps to create a admin user who should restrict role/store/website wise.Is it possible in magento2.0, because as i found this feature was not present in magento1.9(although it was in magento 1.9 EE). Can you suggest me if it is possible then how should i proceed.

Seen this blog and helping many folks in there REST api’s. I am a new bee and need to work on post api’s for complete ecommerce flow. Can i get your help if you pass me one sample custom module which tells about how to add products or customers through rest api using oauth. I will be very grateful to you.

I think the main IDE I see people using is PHP Storm. But there are lots of options – Eclipse has PHP support, NetBeans I think also has support, I personally grew up on vi (now vim), there are tools like codeanywhere.com which are web based development environments. PHP Storm is a paid product, but it is the main tool I hear of being used with Magento development.

Thanks for this article Alan, very helpful. I was wondering if you might be able to comment on what the correct way of returning API errors is for rest endpoints? For example when I want to return a 404 not found for a resource, or InvalidInput etc.

In Magento 1 I believe we would’ve done something along the lines of throwing a Mage_API_Error, but I haven’t quite found the analogous method in Magento 2.

This is really a good practical example of creating new web-services in Magento.
I do have some questions though.

Questions:

– Why methods are limited for anonymous users? If this is the case how protected methods can be tested with tools like SoapUI ?
– How customer is authenticated with SOAP web services? Any other workaround? Even we are not getting password_hash in any response.

Not sure if that’s the one I am looking for. But will definitely give a try.
Main thing what I am trying to do is to login the mobile user using SOAP webservice if they enter the username and password, just like we have in web: /customer/account/login.

Hello Alan, I am trying to learn how to use rest API in Magento 2, and found your example easy to follow. However, I did the Example 1, but it’s not working.
I have Magento 2.1.2 installed over WAMP server. It’s working fine, and cURL it’s also working fine. But when I create the files and folder structure, and then invoke curl, I don’t get the expected result. Indeed, on CMD, I do curl http://127.0.0.1/index.php/rest/V1/calculator/add/1/2, but then I get the index page of wamp, which basically is the wamp’s welcome page; since my Magento URL is 127.0.0.1/magento, I tried to invoke curl http://127.0.0.1/magento/rest/V1/calculator/add/1/2 instead. But then I get the error ‘{“message”:”Request does not match any route.”,”trace”:null}’.

I saw in another comment that maybe registration.php is missing; maybe composer does too?

I am new to Magento and I’m trying to learn by myself, and your tutorial for rest API seems the easiest to follow. Could you please give your opinion on why it’s not working for me?

There may have been some changes since the version when I published the post. Its a problem with blog posts – they are point in time. Have you tried reading the Magento devdocs? We keep that up to date with current versions.