Skip to main content

D365 FO Customer Details Web Service Using X++ Integration API

 

Dynamics 365 FO – Fetch Customer Details Integration API (With Full X++ Code + JSON Explanation)

In this article, I am sharing the complete implementation of my Customer Details Integration API in Microsoft Dynamics 365 Finance & Operations (D365 FO).
This API is designed to:

✔ Fetch a specific customer when CustAccount is provided in the JSON
✔ Fetch all customers when CustAccount is NOT provided
✔ Return multiple addresses and multiple contacts for each customer
✔ Return financial dimensions (BusinessUnit, CostCenter, Department, Project)

How the API Works

1. When CustAccount is provided

If the JSON contains:

 {  
  "DataAreaId": "USMF",  
  "CustAccount": "1101"  
 }  

➡ The API returns only that specific customer's details, including their addresses, contacts, and dimensions.


2. When CustAccount is NOT provided

If JSON is:

 {  
  "DataAreaId": "USMF"  
 }  

➡ The API returns all customers from the selected company (changecompany(DataAreaId) is used).

๐Ÿ“Œ Top-Level Response Structure

The API returns:

  • SuccessMessage

  • ErrorMessage

  • DebugMessage

  • List of Customers

  • Each customer contains:

    • Account, Name, Group, Currency

    • Dimensions

    • List of Addresses

    • List of Contacts

๐Ÿงพ Main Response Contract Class

 [DataContractAttribute]  
 class MZNFRCustomerMainResponse  
 {  
   private List customers;  
   private boolean successMessage;  
   private str errorMessage;  
   private str debugMessage;
   
   [DataMemberAttribute('Customers'),  
   AifCollectionTypeAttribute("Customers",Types::class,classstr(MZNFRCustomerResponse)),  
   AifCollectionTypeAttribute("return",types::class,classstr(MZNFRCustomerResponse))]  
   public List parmCustomers(List _customers = customers)  
   {  
     if(!prmIsDefault(_customers))  
     {  
       customers = _customers;  
     }  
     return customers;  
   }
   
   [DataMember("SuccessMessage")]  
   public boolean parmSuccessMessage(boolean _successMessage = successMessage)  
   {  
     if(!prmIsDefault(_successMessage))  
     {  
       successMessage = _successMessage;  
     }  
     return successMessage;  
   }  
   
   [DataMember("ErrorMessage")]  
   public str parmErrorMessage(str _errorMessage = errorMessage)  
   {  
     if(!prmIsDefault(_errorMessage))  
     {  
       errorMessage = _errorMessage;  
     }  
     return errorMessage;  
   }  
   
   [DataMember("DebugMessage")]  
   public str parmDebugMessage(str _debugMessage = debugMessage)  
   {  
     if(!prmIsDefault(_debugMessage))  
     {  
       debugMessage = _debugMessage;  
     }  
     return debugMessage;  
   }  
 }  

๐Ÿ“จ Customer Request Class (Input from JSON)

 [DataContractAttribute]  
 class MZNFRCustomerRequest  
 {  
   private DataAreaId dataAreaId;  
   private AccountNum custAccount;
   
   [DataMember("DataAreaId")]  
   public DataAreaId parmDataAreaId(DataAreaId _dataAreaId = dataAreaId)  
   {  
     if (!prmIsDefault(_dataAreaId))  
     {  
       dataAreaId = _dataAreaId;  
     }  
     return dataAreaId;  
   } 
   
   [DataMember("CustAccount")]  
   public AccountNum parmCustAccount(AccountNum _custAccount = custAccount)  
   {  
     if(!prmIsDefault(_custAccount))  
     {  
       custAccount = _custAccount;  
     }  
     return custAccount;  
   }  
 }  

๐Ÿ“ Customer Address Response Class

 [DataContractAttribute]  
 class MZNFRCustomerAddressReponse  
 {  
   private str region;  
   private str postalCode;  
   private str street;  
   private str city;  
   private str state;  
   private str country;  
   private boolean isPrimary;  
   
   [DataMember("Region")]  
   public str parmRegion(str _region = region)  
   {  
     if(!prmIsDefault(_region))  
     {  
       region = _region;  
     }  
     return region;  
   }  
   
   [DataMember("PostalCode")]  
   public str parmPostalCode(str _postalCode = postalCode)  
   {  
     if(!prmIsDefault(_postalCode))  
     {  
       postalCode = _postalCode;  
     }  
     return postalCode;  
   }  
   
   [DataMember("Street")]  
   public str parmStreet(str _street = street)  
   {  
     if(!prmIsDefault(_street))  
     {  
       street = _street;  
     }  
     return street;  
   }  
   
   [DataMember("City")]  
   public str parmCity(str _city = city)  
   {  
     if(!prmIsDefault(_city))  
     {  
       city = _city;  
     }  
     return city;  
   }  
   
   [DataMember("State")]  
   public str parmState(str _state = state)  
   {  
     if(!prmIsDefault(_state))  
     {  
       state = _state;  
     }  
     return state;  
   }  
   
   [DataMember("Country")]  
   public str parmCountry(str _country = country)  
   {  
     if(!prmIsDefault(_country))  
     {  
       country = country;  
     }  
     return country;  
   }  
   
   [DataMember("Primary")]  
   public boolean parmIsPrimary(boolean _isPrimary = isPrimary)  
   {  
     if(!prmIsDefault(_isPrimary))  
     {  
       isPrimary = _isPrimary;  
     }  
     return isPrimary;  
   }  
 }  


Customer Contacts Response Class

 [DataContractAttribute]  
 class MZNFRCustomerContactsReponse  
 {  
   private str type;  
   private str description;  
   private str contactNumber;  
   boolean isPrimary;  
   
   [DataMember("Type")]  
   public str parmType(str _type = type)  
   {  
     if(!prmIsDefault(_type))  
     {  
       type = _type;  
     }  
     return type;  
   }  
   
   [DataMember("Description")]  
   public str parmDescription(str _description = description)  
   {  
     if(!prmIsDefault(_description))  
     {  
       description = _description;  
     }  
     return description;  
   }  
   
   [DataMember("ContactNumber")]  
   public str parmContactNumber(str _contactNumber = contactNumber)  
   {  
     if(!prmIsDefault(_contactNumber))  
     {  
       contactNumber = _contactNumber;  
     }  
     return contactNumber;  
   }  
   
   [DataMember("Primary")]  
   public boolean parmIsPrimary(boolean _isPrimary = isPrimary)  
   {  
     if(!prmIsDefault(_isPrimary))  
     {  
       isPrimary = _isPrimary;  
     }  
     return isPrimary;  
   }  
 }  


๐ŸŽฏ Customer Response Class (Full Customer Object)

 [DataContractAttribute]  
 class MZNFRCustomerResponse  
 {  
   private CustAccount custAccount;  
   private Name custName;  
   private CustGroupId custGroupId;  
   private CurrencyCode currency;  
   private str businessUnit;  
   private str costCenter;  
   private str Department;  
   private str Project;  
   private List addresses;  
   private List contacts;  
   
   [DataMember("CustAccount")]  
   public CustAccount parmCustAccount(CustAccount _custAccount = custAccount)  
   {  
     if(!prmIsDefault(_custAccount))  
     {  
       custAccount = _custAccount;  
     }  
     return custAccount;  
   }  
   
   [DataMember("CustName")]  
   public Name parmCustName(Name _custName = custName)  
   {  
     if(!prmIsDefault(_custName))  
     {  
       custName = _custName;  
     }  
     return custName;  
   }  
   
   [DataMember("CustGroupId")]  
   public Name parmCustGroupId(Name _custGroupId = custGroupId)  
   {  
     if(!prmIsDefault(_custGroupId))  
     {  
       custGroupId = _custGroupId;  
     }  
     return custGroupId;  
   }  
   
   [DataMember("Currency")]  
   public CurrencyCode parmCurrency(CurrencyCode _currency = currency)  
   {  
     if(!prmIsDefault(_currency))  
     {  
       currency = _currency;  
     }  
     return currency;  
   }  
   
   [DataMember("BusinessUnit")]  
   public str parmBusinessUnit(str _businessUnit = businessUnit)  
   {  
     if(!prmIsDefault(_businessUnit))  
     {  
       businessUnit = _businessUnit;  
     }  
     return businessUnit;  
   }  
   
   [DataMember("CostCenter")]  
   public str parmCostCenter(str _costCenter = costCenter)  
   {  
     if(!prmIsDefault(_costCenter))  
     {  
       costCenter = _costCenter;  
     }  
     return costCenter;  
   }  
   
   [DataMember("Department")]  
   public str parmDepartment(str _department = department)  
   {  
     if(!prmIsDefault(_department))  
     {  
       department = _department;  
     }  
     return department;  
   }
   
   [DataMember("Project")]  
   public str parmProject(str _Project = Project)  
   {  
     if(!prmIsDefault(_Project))  
     {  
       Project = _Project;  
     }  
     return Project;  
   }  
   
   [DataMemberAttribute('Addresses'),  
   AifCollectionTypeAttribute("Addresses",Types::class,classstr(MZNFRCustomerAddressReponse)),  
   AifCollectionTypeAttribute("return",types::class,classstr(MZNFRCustomerAddressReponse))]  
   public List parmAddresses(List _addresses = addresses)  
   {  
     if(!prmIsDefault(_addresses))  
     {  
       addresses = _addresses;  
     }  
     return addresses;  
   }  
   
   [DataMemberAttribute('Contacts'),  
   AifCollectionTypeAttribute("Contacts",Types::class,classstr(MZNFRCustomerContactsReponse)),  
   AifCollectionTypeAttribute("return",types::class,classstr(MZNFRCustomerContactsReponse))]  
   public List parmContacts(List _contacts = contacts)  
   {  
     if(!prmIsDefault(_contacts))  
     {  
       contacts = _contacts;  
     }  
     return contacts;  
   }  
 }  

๐Ÿง  Main API Service Class


 class MZNFRCustomerService  
 {  
   public MZNFRCustomerMainResponse getCustomerDetails(MZNFRCustomerRequest _request)  
   {  
     MZNFRCustomerMainResponse customerMainResponse = new MZNFRCustomerMainResponse();  
     List customerList = new List(Types::Class);  
     changecompany(_request.parmDataAreaId())  
     {  
       try  
       {  
         CustTable custTable;  
         Query          query = new Query();  
         QueryRun        queryRun;  
         QueryBuildDataSource  qbds;  
         QueryBuildRange     qbr;  
         qbds = query.addDataSource(tableNum(CustTable));  
         
         if (_request.parmCustAccount())  
         {  
           qbr = qbds.addRange(fieldNum(CustTable, AccountNum));  
           qbr.value(_request.parmCustAccount());  
         }  
         
         queryRun = new QueryRun(query);  
         while(queryRun.next())  
         {  
           MZNFRCustomerResponse response = new MZNFRCustomerResponse();  
           custTable = queryRun.get(tableNum(CustTable));  
           response.parmCustAccount(custTable.AccountNum);  
           response.parmCustName(custTable.name());  
           response.parmCustGroupId(custTable.CustGroup);  
           response.parmCurrency(custTable.Currency);  
           response.parmBusinessUnit(this.getDimensionDisplayValue(custTable.DefaultDimension,"BusinessUnit"));  
           response.parmCostCenter(this.getDimensionDisplayValue(custTable.DefaultDimension,"CostCenter"));  
           response.parmDepartment(this.getDimensionDisplayValue(custTable.DefaultDimension,"Department"));  
           response.parmProject(this.getDimensionDisplayValue(custTable.DefaultDimension,"Project"));  
           
           CustTable custTableAddress;  
           DirPartyLocation    dirPartyLocation;  
           LogisticsLocation   logisticsLocation;  
           LogisticsPostalAddress logisticsPostalAddress;  
           LogisticsElectronicAddress logisticsElectronicAddress;  
           List customerAddressList = new List(Types::Class);  
           
           while select custTableAddress  
           where custTableAddress.AccountNum == custTable.AccountNum  
           join dirPartyLocation  
           where dirPartyLocation.Party == custTableAddress.Party  
           join logisticsLocation  
           where logisticsLocation.RecId == dirPartyLocation.Location  
           join logisticsPostalAddress  
           where logisticsPostalAddress.Location == logisticsLocation.RecId  
           {  
             MZNFRCustomerAddressReponse addressReponse = new MZNFRCustomerAddressReponse();  
             addressReponse.parmRegion(logisticsPostalAddress.CountryRegionId);  
             addressReponse.parmPostalCode(logisticsPostalAddress.ZipCode);  
             addressReponse.parmStreet(logisticsPostalAddress.Street);  
             addressReponse.parmCity(logisticsPostalAddress.City);  
             addressReponse.parmState(logisticsPostalAddress.State);  
             addressReponse.parmCountry(logisticsPostalAddress.County);  
             addressReponse.parmIsPrimary(dirPartyLocation.IsPrimary);  
             customerAddressList.addEnd(addressReponse);  
           }  
           
           response.parmAddresses(customerAddressList);  
           List customerContactsList = new List(Types::Class);  
           logisticsElectronicAddress.clear();  
           dirPartyLocation.clear();  
           
           while select * from logisticsElectronicAddress  
           join dirPartyLocation  
           where logisticsElectronicAddress.Location == dirPartyLocation.Location  
           && dirPartyLocation.Party == custTable.Party  
           {  
             MZNFRCustomerContactsReponse contactsReponse = new MZNFRCustomerContactsReponse();  
             contactsReponse.parmType(enum2Str(logisticsElectronicAddress.Type));  
             contactsReponse.parmDescription(logisticsElectronicAddress.Description);  
             contactsReponse.parmContactNumber(logisticsElectronicAddress.Locator);  
             contactsReponse.parmIsPrimary(logisticsElectronicAddress.IsPrimary);  
             customerContactsList.addEnd(contactsReponse);  
           }  
           
           response.parmContacts(customerContactsList);  
           customerList.addEnd(response);  
         }  
         
         customerMainResponse.parmCustomers(customerList);  
         customerMainResponse.parmSuccessMessage(true);  
         customerMainResponse.parmDebugMessage("Customer details fetched successfully");  
       }  
       catch (Exception::CLRError)  
       {  
         System.Exception interopException = CLRInterop::getLastException();  
         customerMainResponse.parmSuccessMessage(false);  
         customerMainResponse.parmErrorMessage(interopException.ToString());  
       }  
     }  
     return customerMainResponse;  
   }  
   
   public str getDimensionDisplayValue(RecId defaultDimension, Name _dimensionName)  
   {  
     DefaultDimensionView DefaultDimensionView;  
     select firstonly1 DefaultDimensionView  
     where DefaultDimensionView.Name == _dimensionName  
     && DefaultDimensionView.DefaultDimension == defaultDimension;  
     str value = DefaultDimensionView.dimensionDiscription();  
     return DefaultDimensionView.dimensionDiscription();  
   }  
 }  


๐ŸŽ‰ Conclusion

This API is flexible, fast, and extremely useful for integrating external systems with Dynamics 365 Finance & Operations.
It supports:

✔ Single customer fetch
✔ Bulk customer fetch
✔ Multi-address response
✔ Multi-contact response
✔ Full dimensions mapping

Popular posts from this blog

Consuming an External REST API in Dynamics 365 Finance & Operations (X++) and Saving Data into a Custom Table

  Introduction In this blog, I will explain how I consumed an online REST API in Microsoft Dynamics 365 Finance & Operations (D365 FO) using X++ and CLR Interop , deserialized the JSON response using Data Contract classes , and finally stored the data in a custom table . This is a common real-world integration scenario where external systems expose data via REST APIs and D365 FO needs to consume and persist that data. For this demo, I used the following public REST API : API URL: https://api.restful-api.dev/objects Solution Overview The solution consists of four X++ classes : MZNFRConsumeRestAPI – Main logic class (API call, deserialization, DB insert) MZNFRConsumeRestAPIParentContract – Parent contract (wraps JSON list) MZNFRConsumeRestAPIHeaderContract – Header contract (id, name) MZNFRConsumeRestAPILinesContract – Child contract (nested data node) ๐Ÿ“Œ Class Structure Diagram API Response Structure The API returns a JSON array like this: [ { ...

How to Create a Custom Data Entity in Dynamics 365 Finance and Operations

  Creating a Data Entity in Dynamics 365 Finance and Operations (D365FO) is essential for enabling data import/export, integrations, and public APIs. In this blog, we’ll walk through both the basic shortcut method and the advanced method for creating a custom Data Entity, using a table named MZNFRCustomTable as our example. ๐Ÿงฉ Step 1: Create a Custom Table Start by creating a new table in your project. Go to Solution Explorer → Right-click your project → Add New Item Select Table under FinanceOperations > Data Model Name it MZNFRCustomTable ๐Ÿงฑ Step 2: Add Fields to Your Table Define the fields you need in your table. For example: CustomerAccount CustomerName Currency PaymentNote Balance  Step 3: Shortcut Method – Create Data Entity via Addins Right-click on your table → Addins → Create Data Entity This method is quick but limited in customization. ❗ Common Error If you haven’t created an index, you’ll get this error: "The natural key for the table MZNFRCusto...

Modified Field Method in X++

  In Dynamics 365 Finance and Operations, user experience is everything. If a user enters an ItemId , they shouldn't have to manually type the ItemName . We can automate this using the modifiedField override on the table level. The ModifiedField  method is a kernel method that triggers after a field's value has been changed. It's the perfect place to put logic for fetching related data or resetting dependent fields. The Implementation On your custom table MNZFRSalesLine , you will override the  ModifiedField  method. We use a switch  statement on the _fieldId parameter to ensure our code only runs when the ItemId is the field being touched. public void modifiedField(FieldId _fieldId) { super(_fieldId); switch (_fieldId) { case fieldNum(MNZFRSalesLine, ItemId): if (this.ItemId) { // Fetch the Item Name using the InventTable find method // inventTable.itemName() is a common helper method ...