D365 FO Integration using oData/REST API

This blog describes the method to interact with the Dynamics 365 Unified Operation using oData. Dynamics 365 UO provides REST API to interact with Data via Data Entities.

oData: Open Data Protocol (OData) is a standard protocol for consuming data exposed by Dynamics 365 for Operations. OData is a new Representational State Transfer (REST) based protocol for CRUD operations – C-Create, R-Read, U-Update and D-Delete – that allows for integrating with Dynamics 365 for Operations. It is applied to all types of web technologies, such as HTTP and JavaScript Object Notation (JSON).

Data Entity: A data entity in D365 is an abstraction from the physical implementation of database tables. A data entity is a simplified de-normalized representation of underlying tables. A data entity represents a common data concept or functionality, (e.g. Vendors V2  where the details are stored in normalized relational tables) but all details are represented in one flat view in Vendor Details data entity.

The data flow for querying data using oData:

Dynamics 365 UO: Integration using oData/REST API

Dynamics 365UO: oData Features

  1. CRUD operations are handled through HTTP verb support for POST, PATCH, PUT, and DELETE.
  2. The D365 UO for UO supports paging and maximum page size is 1,000.
  3. Available query options are:
  4. Filter Options are: Equals, Not equals, Greater than, Greater than or equal, Less than, Less than or equal, And, Or, Not
  5. D365 FO provides option to query data from Cross-company

URI Conventions for oData in Deatil has been described here

Querying Data Cross-Company

By default, OData returns only data that belongs to the users default company. To query the data from outside the users default company, specify the following keyword ?cross-company=true in the query. This option will return data from all companies that the user has access to.

Example: http://[baseURI]/data/PurchaseOrderHeadersV2?cross-company=true

To filter by a particular company that isn’t your default company, use the following syntax:

http://[baseURI]/data/PurchaseOrderHeadersV2?$filter=dataAreaId eq 'usrt'&cross-company=true

Azure Active Directory Authentication

In order to call the D365 UO oData EndPoints, it is necessary to authenticate with a valid access token. The token can be retrieved from Azure Active Directory using a valid Application Id and secret key, which has access to the D365FO environment. The application ID and secret key are created by registering an application in Azure Active directory.

Pre-requisite :

  1. Register an application in Azure AD and Grant access to D365FO. The detailed steps are described here. Instead of Dynamics CRM  select Dynamics ERP 
  2. Register the AAD application in D365FO
    • System administration > Setup > Azure Active Directory applications
    • Click “New” -> Enter APP-ID(created as part of the previous step), Meaningful name and User ID (the permission you would like to assign).
Dynamics 365 UO: Integration using oData/REST API
  1. The client application authenticates to the Azure AD token issuance endpoint and requests an access token.
  2. The Azure AD token issuance endpoint issues the access token.
  3. The access token is used to authenticate to the D365FO DMF and initiate DMF Job.
  4. Data from the DMF is returned to the third-party application.
Http Method: POST
Request URL: https://login.microsoftonline.com//oauth2/token 
Parameters : grant_type: client_credentials [Specifies the requested grant type. In a Client Credentials Grant flow, the value must be client_credentials.]
client_id: Registered App ID of the AAD Application 
client_secret: Enter a key of the registered application in AAD.
Resource: Enter the URL of the D365FO Url (e.g. https://dev-d365-fo-ultdeabc5b35da4fe25devaos.cloudax.dynamics.com)

The Resource URL should not have “/” in the end, othwerise you would always get access denied while accessing the target resource

C# Code

//Azure AAD Application settings
//The Tenant URL (use friendlyname or the TenantID
static string aadTenant = "https://login.windows.net/dev.onmicrosoft.com";
//The URL of the resource you would be accessing using the access token.Please ensure / is not there in the end of the URL
static string aadResource = "https://dev-testdevaos.sandbox.ax.dynamics.com";
//APplication ID . Store them securely / Encrypted config file or secure store
static string aadClientAppId = "GUID Of the Azure application";
//Application secret. Store them securely / Encrypted config file or secure store
static string aadClientAppSecret = "Secret of the Azure application"; 
/// Retrieves an authentication header from the service.The authentication header for the Web API call.        private static string GetAuthenticationHeader()
            //using Microsoft.IdentityModel.Clients.ActiveDirectory;
            AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
            var creadential = new ClientCredential(aadClientAppId, aadClientAppSecret);
            AuthenticationResult authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, creadential).Result;
            return authenticationResult.AccessToken;

CRUD Operations on Data Entities

The following code shows the example for Creating, Reading, Updating and Deleting a PuchaseOrderHeader entity. More detailed information on oData can be found here

 private static async void CRUDonPurchaseOrderHeader()
            string authHeader = GetAuthenticationHeader();
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri(aadResource);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authHeader);
            //Initiate the PurchaseOrderHeader object
            var payload = new PurchaseOrderHeader()
                PurchaseOrderNumber = "001-000234",
                DataAreaId = "001",           
                OrderVendorAccountNumber = "000001",
                DeliveryAddressCountryRegionId = "NL",
                DeliveryAddressDescription = "Business Location",
                AccountingDate = "2019-09-19T12:00:00Z",
                PurchaseOrderName = "JDE Professional",
                RequestedDeliveryDate = "2019-09-19T12:00:00Z",
                ExpectedStoreAvailableSalesDate = "2019-09-19T12:00:00Z",
                ConfirmedDeliveryDate = "2019-09-19T12:00:00Z",
                ExpectedStoreReceiptDate = "2019-09-19T12:00:00Z",
                FixedDueDate = "2019-09-19T12:00:00Z",
                ExpectedCrossDockingDate = "2019-09-19T12:00:00Z"
            var stringPayload = JsonConvert.SerializeObject(payload);
            var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
            var result = client.PostAsync("/data/PurchaseOrderHeadersV2", httpContent).Result;
            string resultContent = await result.Content.ReadAsStringAsync();
            JObject joResponse = JObject.Parse(resultContent);
            //Get a Purchase Order
             result = client.GetAsync("/data/PurchaseOrderHeadersV2?$filter=PurchaseOrderNumber eq '001-000234'").Result;
             resultContent = await result.Content.ReadAsStringAsync();
             joResponse = JObject.Parse(resultContent);
            //Update the PurchaseOrderHeader object
            payload = new PurchaseOrderHeader()
                PurchaseOrderNumber = "001-000233",
                DataAreaId = "001",
                OrderVendorAccountNumber = "000001",
                DeliveryAddressCountryRegionId = "NL",
                DeliveryAddressDescription = "Business Location Address changed",
                AccountingDate = "2019-09-19T12:00:00Z",
                PurchaseOrderName = "JDE Professional",
                RequestedDeliveryDate = "2019-09-19T12:00:00Z",
                ExpectedStoreAvailableSalesDate = "2019-09-19T12:00:00Z",
                ConfirmedDeliveryDate = "2019-09-19T12:00:00Z",
                ExpectedStoreReceiptDate = "2019-09-19T12:00:00Z",
                FixedDueDate = "2019-09-19T12:00:00Z",
                ExpectedCrossDockingDate = "2019-09-19T12:00:00Z"
             stringPayload = JsonConvert.SerializeObject(payload);
             httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
             result = client.PatchAsync("/data/PurchaseOrderHeadersV2(dataAreaId='001',PurchaseOrderNumber='001-000234')", httpContent).Result;
            //Delete the PurchaseOrderHeader object          
            result = client.DeleteAsync("/data/PurchaseOrderHeadersV2(dataAreaId='001',PurchaseOrderNumber='001-000234')").Result;

Published by Poojith Jain

Poojith Jain is an Azure Architect with good experience with software design and development. He has a thorough knowledge of Azure Integration and he is passionate about solving complex and challenging problems in the field of Azure

4 thoughts on “D365 FO Integration using oData/REST API

  1. Do you have an example of posting multiple headers in a single post transaction (i.e. the payload would be an array)? Or posting the header and multiple lines in a single post transaction.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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

%d bloggers like this: