How to call M3 API with REST & JSON

With the Lawson Grid, M3 introduced a new REST endpoint for calling M3 API where the response is returned in XML or in JSON.

Background

Historically, software clients that called M3 API needed to use proprietary libraries, such as MvxSockJ.class for Java and MvxSockX_SVR.dll for Microsoft languages. Over the years, Intentia introduced Movex Web Services to call M3 API using the SOAP standard. Then, Lawson introduced the Grid for cloud computing. In the process, Lawson replaced IBM WebSphere Application Server by open source software, it also replaced the Core Web Services for Lawson Smart Office, and it added Jersey, an open source implementation of JAX-RS (JSR 311). The positive outcome is that now, software clients can call M3 API using REST/JSON.

Why does it matter?

REST is increasingly popular, and years ago it started supplanting SOAP for web services [1].

Also, JSON is becoming the de facto data-interchange format, nicknamed “The Fat-Free Alternative to XML” [2].

Most importantly, it’s now possible to call M3 API without needing the proprietary libraries, and without needing Lawson Web Services. Software clients can now make simple HTTP Requests to call M3 API. This makes it easier to call M3 API from third-party software (such as Tibco, Business Objects, Web Methods, etc.) and from previously unsupported or difficultly supported programming languages (such as JScript.NET, JavaScript, Ruby, PHP, etc.).

What about Lawson Web Services?

The new REST endpoint for M3 API doesn’t preclude using Lawson Web Services. Lawson Web Services is still necessary for calling M3 API with the SOAP protocol, and for using the unavoidable M3 Display Program adapter which cannot be found in any other Lawson product. Also, Lawson Web Services is still necessary for Lawson Smart Data Tool.

REST endpoint

To access the new REST endpoint for M3 API, open a Grid Information page, which is accessible from any participating Host in the Grid:

For example: http://ussplu123:20005/grid/info.html

At the bottom of the page is the Application Name M3-API-WS, and it has a Link to the Rest Service‘s WADL:

For example: http://ussplu123:20005/m3api-rest/application.wadl

This WADL gives us the resource path:

execute/{program}/{transaction}

Where {program} is the M3 API Program, like MNS150MI, CRS610MI, MMS200MI, etc. And where {transaction} is that API’s transaction, like GetBasicData, AddAddress, LstByNumber, etc.

Let’s try to call the API CRS610MI.LstByNumber. The URL in my example would be: http://ussplu123:20005/m3api-rest/execute/CRS610MI/LstByNumber

The server will challenge us for HTTP Basic Authentication, we need to provide a valid M3 userid and password:

And the result in XML is a collection of MIRecord elements with Name/Value pairs:

JSON

We can get the result in JSON by adding the HTTP Header field:

Accept: application/json

The result is:

REST GUI

RESTClient is a great REST graphical user interface which we can use to test M3 API; you can download it at http://code.google.com/p/rest-client/

Paste the URL, set the M3 userid and password in the Authentication tab, optionally set the Header to get the response in JSON, and click Go!

Conclusion

With the Lawson Grid, software clients can now natively call M3 API from third-party software, and from previously unsupported or difficultly supported programming languages, without the need for proprietary libraries, and without the need for Lawson Web Services.

Updates

Input parameters

UPDATE 2012-07-17: You can set input parameters in the URL, for example: http://hostname:port/m3api-rest/execute/MMS200MI/GetItmBasic?CONO=531&ITNO=ABCDEF . Also, you can hook RESTClient to use Fiddler as a proxy in Tools >;; Options.

Max number of records

UPDATE 2012-07-31: You can set the maximum number of records returned with the matrix parameter maxrecs, for example: http://hostname:port/m3api-rest/execute/CRS610MI/LstByNumber;maxrecs=20?CONO=1

Output fields

UPDATE 2012-08-02: You can set the output fields returned with the parameter returncols, for example: http://hostname:port/m3api-rest/execute/CRS610MI/LstByNumber;returncols=CUNO,CUNM,CUA1,STAT,PHNO

Related articles

Published by

thibaudatwork

ex- M3 Technical Consultant

60 thoughts on “How to call M3 API with REST & JSON”

  1. I am using this blog as a base to call Movex REST Web Services using .NET. So far I am able to call GET methods but when it comes to calling POST transactions there is no documentation and I am really struggling to get it working. Anyone who has done it can you please post some sample code ?

    Like

    1. Hi Virang. If you look at the WADL, the /execute/{program}/{transaction} only accepts the GET method, not POST, hence your struggle. Look at the WADL. There seems to be another resource executeRawPost that accepts POST; I don’t know what it does. Also, I suggest you try in RESTClient before adding a layer of complexity with .NET. /Thibaud

      Like

  2. When calling M3 REST APIs is Basic Authentication is the only authentication mechanism available ? When I try to execute M3 REST API from Soap UI and when the call fails it shows WWW Autorization as Negotiate, NTLM and Basic Realm=”TEST” in response header. Does that mean it supports NTLM and Basic authentication or just Basic Authentication as described in the article ?

    Like

  3. Hi Thibaud,

    I am trying to use Angular JS to display data to a page. When I access an m3 api via

    http:// {server url}:20005/m3api-rest/execute/CRS610MI/LstByNumber;maxrecs=2?

    I am able to get the data returned {with program, transaction, metadata and MIRecord array}, but I can’t extract the data’s specific contents:
    Either by accessing the array

    1{{row.CUNO}}
    2{{row.CUNM}}
    3{{row.CUA1}}

    nor from the single header detail in the java script $scope.data.Program.

    Would you have any idea here?

    Like

    1. It seems M3 is not replying in json format.
      This is what I get:
      {“RowIndex”:”0″,”NameValue”:[{“Name”:”CONO”,”Value”:”100″},{“Name”:”DIVI”,”Value”:”TMW”},{“Name”:”LNCD”,”Value”:”GB”},

      Not
      { “CONO”: “100”,
      “DIVI”: “TMW” ,
      “LNCD” : “GB”

      Like

      1. Hi Jonathan, the M3 API REST Web Service does return valid JSON. It might not be an associative array you can conveniently access by key (I too wish it was), but it’s still a collection of objects you can easily traverse. Go to my other blog post on M3 API with Dojo Toolkit, and read my source code, I transform the response into an associative array with the JavaScript Array.map function: https://thibaudatwork.wordpress.com/2013/07/09/how-to-call-m3-api-using-the-dojo-toolkit/ . With it you can more conveniently do record[‘CUNO’]. /Thibaud

        Like

    2. Hi Jonathan,

      Make sure you have same version of M3 Grid in your Development and Production environment. I developed one application with M3 REST API calls just to find out it failed in Production due to difference of Grid version and how it returns M3 API results in different JSON format. I called MMS200MI – GetUserData API in one environment it returned result as an array of values while in another one it is just one record.

      I have to do something like below based on response

      using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
      {
      if (response.StatusCode == HttpStatusCode.OK)
      {
      StreamReader readert = new StreamReader(response.GetResponseStream());
      string x = readert.ReadToEnd();
      JObject jObject = JObject.Parse(x);
      //var def = new { Name = “”, Value = “” };
      var def = new List();

      if (x.Contains(“\”MIRecord\”:[“)) // Result as an array of objects
      {
      var nvp = JsonConvert.DeserializeAnonymousType(jObject[“MIRecord”][0][“NameValue”].ToString(), def);

      Dictionary propVal = new Dictionary();
      foreach (var sel in nvp)
      {

      propVal.Add(sel.Name, sel.Value.Trim());
      }
      //propVal.Add(nvp.Name,nvp.Value);
      result = propVal;

      }
      else // Just one result
      {
      var nvp = JsonConvert.DeserializeAnonymousType(jObject[“MIRecord”][“NameValue”].ToString(), def);

      Dictionary propVal = new Dictionary();
      foreach (var sel in nvp)
      {

      propVal.Add(sel.Name, sel.Value.Trim());
      }
      //propVal.Add(nvp.Name,nvp.Value);
      result = propVal;
      }
      }
      }

      Like

  4. Thanks for pointing me in the right direction!

    Although I had to map the json data differently

    $scope.allCustomers={};// customer 0-x…
    var oneRecord = {}; //for each customer fields, field:value ex CUNO:100
    //GetData
    $http.get($scope.apiURL).success(function(data){
    $scope.MIRecords = data.MIRecord;//remove metadata and headers
    //Parse to a map
    for (var i in $scope.MIRecords) {//get records
    oneRecord={};
    //get fields and values
    for (var j in $scope.MIRecords[i].NameValue){
    oneRecord[$scope.MIRecords[i].NameValue[j].Name] //field name
    = $scope.MIRecords[i].NameValue[j].Value; //field value
    }
    $scope.allCustomers[i]=oneRecord;
    }

    Like

  5. UPDATE: We can force the CONO and DIVI by using lowercase cono and divi parameters in the URI path segment with semicolons. I found it here: go to the Infor ION Grid Management Pages > Node M3ApiWS > Module MI-WS > Global Management Pages (M3-API-WS) > Test; click Settings; set the Execution Settings: Company (cono), Division (divi), Max returned records, Date Format, and Run As User; then click Show As REST; it will show the full URL, e.g. /m3api-rest/execute/CRS610MI/Add;cono=702;divi=USA;maxrecs=100?CUTM=ACME&CUNM=Thibaud

    Like

  6. I’m reading through this and wondering if anyone has tested using the Post methods? If so,can you provide any direction? Thanks!

    Like

    1. Actually, I looked a bit more. According to the WADL you can POST JSON to /m3api-rest/execute. And according to the WSDL you can POST SOAP to /m3api/MIAccess?wsdl . I haven’t looked further. Use SoapUI to test. Anyway, why do you need POST, in other words why is GET not sufficient?

      Like

      1. Thibaud,

        Thanks for the response. We want to use REST web services to push and update data in M3. Is there another way we should look at doing this?

        Like

        1. I do not understand. Does it not work with GET as explained in the blog post above? Inserts and updates in the REST style are supposed to happen only on POST, not GET, but I do not think the M3 API is that RESTful strict. I have not tried but it should work. Did you try?

          Like

    1. Hola Gaston. You cannot set the username:password in the URL, at least not anymore; in the past you could do it as per the URL specification, but since then the web evolved in terms of security restrictions, for instance URLs are saved in browser bookmarks and server logs, so it’s unsafe to set the username:password in the URL. Now you have to use it in the Authorization header. The example is in the Fiddler screenshot where it says “Authorization: Basic”. The value is the username:password, separated by a colon and Base64-encoded. The Infor Grid has other authentication mechanisms which I have not tried, I think NTLM/Kerberos or some other stuff like that.

      Like

  7. Hola Thibaud,

    In C#, I am using the HTTPWebRequest and abled to get the response value but when I tried to parse it using JOBject then I get an unhandled exception, will that be because it is not a valid JSon and what would you recommend to get the array of values? see my code below which I flowed the Jonathan’s example.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Net;
    //
    using System.IO; //fro the stream reader
    using Newtonsoft.Json.Linq;
    using Newtonsoft.Json;
    using System.Collections.Concurrent;
    using System.Collections;
    //
    var url = “http://TEST.my.com:33333/m3api-rest/execute/PMS230MI/Select;ma…. “;
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.Credentials = new NetworkCredential(“gaston”, “myfakepwd”);
    var response = (HttpWebResponse)request.GetResponse();
    using(response) {
    string responseValue = string.Empty;
    if (response.StatusCode == HttpStatusCode.OK) {
    StreamReader readert = new StreamReader(response.GetResponseStream());
    string x = readert.ReadToEnd();

              JObject jnlObject = JObject.Parse(x); 
              // *** ERROR: JsonReaderException was unhandled
               // *** ERROR: An unhandled exception of type 'Netwtonsoft.Json.JasonReaderException'
               //*** ERROR : occurred in Newtonsoft.json.dll
    
                List<string> defnl = new List<string>(); 
                var nvp = JsonConvert.DeserializeAnonymousType(jnlObject["MIRecord"]    
                ["NameValue"].ToString(), defnl); 
       }
    

    }

    Like

    1. Gaston, I do not know the answer right now; I would have to look. What you are doing is all standard stuff, REST/JSON/C#, so you can get help on the Internet, nothing is proprietary. As for the validity of the JSON, it should be valid, you can check yours with JSONlint. I’m sorry that’s all I can help with right now.

      Like

    2. Put a break point at string x and see what is the content of x before parsing it to JSON. It could be that you are not getting proper response string.

      Like

    3. This is one of my recent implementation. There is a difference in JSON response from different M3 Grid version so you may have to tweak it. Exception handling is changed in recent version of M3. It used to generate NOK in WebException which is no longer the case.

      try
      {
      HttpWebRequest request = WebRequest.Create(url + “;returncols=” + returncols + “;maxrecs=” + maxrecords + “?” + inputValues) as HttpWebRequest;
      request.Credentials = new NetworkCredential(username, password);

      request.Accept = "application/json";
      // Get response  
      using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
      {
          if (response.StatusCode == HttpStatusCode.OK)
          {
              StreamReader readert = new StreamReader(response.GetResponseStream());
              string x = readert.ReadToEnd();
              JObject jObject = JObject.Parse(x);
      
              #region Snippet Added To Handle NOK
              //TODO : Snippet added to resolve response difference in new REST version - NOK Error no longer in Exception 
              if (jObject["@type"] != null)
              {
                  if (jObject["@type"].ToString() == "ServerReturnedNOK")
                  {
                      throw new WebException((jObject["Message"] != null ? jObject["Message"].ToString() : "Error calling M3 API"));
                  }
              }
              #endregion
              if (jObject["MIRecord"] != null)
              {
                  for (int i = 0; i < jObject["MIRecord"].Count(); i++)
                  {
                      Dictionary<string, string> propVal = new Dictionary<string, string>();
                      JArray allNameValues = null;
      
                      if (x.Contains("\"MIRecord\":["))
                      {
                          allNameValues = ((JArray)jObject["MIRecord"][i]["NameValue"]);
                      }
                      else
                      {
                          if (i == 0) continue;
                          allNameValues = ((JArray)jObject["MIRecord"]["NameValue"]);
                      }
      
                      var selected = allNameValues.Children();
                      var def = new { Name = "", Value = "" };
                      foreach (var sel in selected)
                      {
                          var nvp = JsonConvert.DeserializeAnonymousType(sel.ToString(), def);
                          propVal.Add(nvp.Name.Trim(), nvp.Value.Trim());
                      }
      
                      if (!string.IsNullOrEmpty(propVal["FV03"]))
                      {
                          if (propVal["FV03"] == "KEY_TXT")
                          {
                              string textId = propVal["FV08"];
                          }
                      }
      
                  }
              }
          }
      }
      

      }
      catch (WebException wex)
      {
      error = (wex.Response != null ? ((HttpWebResponse)wex.Response).StatusDescription : wex.Message);
      if (error.ToUpper().Contains(“RECORD DOES NOT EXIST”))
      {
      error = “”;
      }
      // throw;

      }
      catch (Exception ex)
      {
      error = ex.Message;
      }

      Like

  8. Hi Virang,
    I was missing basic settings on my header like “request.Accept = application/json” but with your illustration I was able to get it working.

    Thank you,
    Gaston

    Like

  9. NOTE: The M3 API WS in M3 13.4 released September 2016 now supports CORS (Cross-Origin Resource Sharing, which was missing in the HTTP response), and supports Swager at m3api-rest/swagger/{MIProgram}. More information at M3 Core Infrastructure and Technology Release Notes Version 13.4.0 Published September 6, 2016.

    Like

  10. Is there a way to set maxrecs to list all records? If i take maxrecs out it will only return 99 records max. If i add say 5000 ill get 5000 but I don’t always know the amount of records..

    Like

    1. Hello,
      I don’t work with M3 but I’m doing an integration with M3 Cloud. Our REST client is going to consume data from a custom M3 API program via REST call.
      This custom program would export transactional data. In the worst case, they estimate that volume of data would be 1.500.000 transactional records.

      Any experience with this kind of calls so can add feedback about potential issues exporting that amount of data from a custom M3 API program being called remotely with REST?

      And let’s say we have 40 divisions running the same program (with different inputs). We would be talking about 50 millions of records in total from different calls…

      For M3 Cloud, wouldn’t be any other method apart from the REST calls that would be more efficient? We can have java code on our side to call the API if needed.

      Any feedback is more than welcome.

      Thanks

      Like

      1. Hi Francisco,

        I did not work with tuning M3 performance, so I am not the best person to respond to your question. And I do not work with M3 anymore, so I am not aware of the latest answers for your question. And I used to work primarily with on-premise customers, not so much with cloud customers, so I do not have metrics for the cloud.

        Nonetheless, my gut feeling tells me the M3 cloud is built with reliability, high availability, scalability and fail-over as top priorities, and comparatively, with performance as a secondary priority, not the reverse. That means your 1.5 million query will probably succeed, just not as fast as you may want it to.

        My suggestions are the following:

        1) Do some tests and find the throughput of your M3 (for example, you may find it can handle no more than 100 queries per second), and throttle your queries accordingly so they occur not faster than the server can respond to (for example, after every query, force a pause of 10ms, so that there are maximum 100 queries per second; your 1.5M query will complete in 1.5M/100 in seconds, that’s about four hours).

        2) Query only static data such as product names, do not query transactional data like available-to-promise (although you said you need transactional data), because it is expensive to compute in terms of CPU, memory, disk access, and database.

        3) Optimize your implementation by introducing a cache between client and server to prevent repeated queries from hitting the server. I do not know that there is a turn-key cache solution for M3 API, but there are plenty of cache software available on the Internet.

        The Infor M3 team has a team dedicated to performance. Try getting in touch with them through your Infor channels (e.g. Infor project manager, technical project manager, Infor Support).

        At the following link is some work I did in the past for adding data to M3 on-premise, not exactly your question, but you may find some answers: https://m3ideas.org/2016/03/17/poll-how-to-add-80-million-itemwarehouse-records-in-m3/

        As for the entry points, I think with the cloud you only get the REST entry point. If it were on-premise, you would be able to call M3 directly with the binary M3 API protocol, or even faster, query the database itself.

        Another idea: export the database (I don’t know if Infor offers that option for cloud customers), and query the offline database on your computer directly.

        Hope it helps.

        Thibaud

        Like

  11. I’m making a GET call from a mashup to a third party API that is returning JSON. However, I am not able to display the response in a mashup. Using Fiddler, I can confirm a JSON response. However, the Smart Office log says there is no response.

    Like

  12. Hi all,

    Thank you for the detailed descriptions. It is a big help!

    I would like to ask about the rest string when we have to send transactions to a blank division. I try to send DIVI=&… or DIVI=null&…

    Can you please share how to do it in the correct way?

    Thank you!

    Like

    1. I haven’t worked with this in 10 years. I remember there was a trick to force a value to blank, but I don’t remember how. Try 3 asterisks *** or 3 question marks ??? .

      Like

Leave a comment