Domain 6 β€” Module 3 of 5 60%
23 of 28 overall
Domain 6: Integrate and Manage Data Solutions Free ⏱ ~16 min read

Custom Services, REST & SOAP

Build custom service endpoints in F&O, consume external REST APIs from X++, work with SOAP services, leverage Electronic Reporting for integration, and use the Batch OData API.

Custom Services, REST & SOAP

OData and DMF handle most integration needs. But sometimes you need a custom endpoint β€” one that runs specific business logic, validates complex input, or returns a tailored response. And sometimes F&O needs to call out to an external API. This module covers both directions: building endpoints for others to call, and calling external services from X++.

Simple explanation

Think of it like a restaurant.

OData is the buffet β€” standard dishes, self-service, pick what you want. Custom services are the Γ  la carte menu β€” you define exactly what the kitchen prepares and how it is served. You write the β€œrecipe” (service class), wrap it in a β€œmenu” (service group), and publish it so external systems can order.

Going the other direction β€” when F&O needs to call an external API (like checking a tax service or sending an SMS) β€” that is like the restaurant phoning a supplier to get fresh ingredients mid-service.

Building Custom Services

A custom service in F&O consists of three pieces:

  1. Data contracts β€” classes that define the request and response structure
  2. Service class β€” the business logic (methods the external system calls)
  3. Service group β€” the deployment unit that exposes the service as an endpoint

Step 1: Data Contracts

Data contracts use the DataContractAttribute and DataMemberAttribute to define the shape of request and response objects.

[DataContractAttribute]
class InventoryCheckRequest
{
    ItemId itemId;
    InventLocationId warehouseId;

    [DataMemberAttribute('ItemId')]
    public ItemId parmItemId(ItemId _itemId = itemId)
    {
        itemId = _itemId;
        return itemId;
    }

    [DataMemberAttribute('WarehouseId')]
    public InventLocationId parmWarehouseId(InventLocationId _warehouseId = warehouseId)
    {
        warehouseId = _warehouseId;
        return warehouseId;
    }
}

[DataContractAttribute]
class InventoryCheckResponse
{
    ItemId      itemId;
    real        availableQty;
    str         status;

    [DataMemberAttribute('ItemId')]
    public ItemId parmItemId(ItemId _itemId = itemId)
    {
        itemId = _itemId;
        return itemId;
    }

    [DataMemberAttribute('AvailableQuantity')]
    public real parmAvailableQty(real _qty = availableQty)
    {
        availableQty = _qty;
        return availableQty;
    }

    [DataMemberAttribute('Status')]
    public str parmStatus(str _status = status)
    {
        status = _status;
        return status;
    }
}

What’s happening:

  • DataContractAttribute marks the class as serialisable for service communication
  • DataMemberAttribute('Name') defines the JSON/XML property name
  • The parm method pattern is the standard X++ getter/setter β€” the optional parameter sets the value, the return gets it

Step 2: Service Class

The service class contains the actual business logic. Methods decorated with AifCollectionTypeAttribute (for collections) are exposed as service operations.

class InventoryCheckService
{
    [AifCollectionTypeAttribute('return', Types::Class)]
    public InventoryCheckResponse checkAvailability(InventoryCheckRequest _request)
    {
        InventoryCheckResponse response = new InventoryCheckResponse();
        InventSum inventSum;

        select sum(AvailPhysical) from inventSum
            where inventSum.ItemId == _request.parmItemId()
            && inventSum.InventLocationId == _request.parmWarehouseId();

        response.parmItemId(_request.parmItemId());
        response.parmAvailableQty(inventSum.AvailPhysical);
        response.parmStatus(inventSum.AvailPhysical > 0 ? "InStock" : "OutOfStock");

        return response;
    }
}

πŸ—οΈ Vik’s code review note: β€œAlways validate input in your service class. If someone passes an empty ItemId, don’t let it hit the database β€” throw a meaningful error. The exam tests whether you know to validate request parameters before processing.”

Step 3: Service Group

A service group is a metadata element that you create in Visual Studio:

  1. Add new item β†’ Service Group (name: InventoryCheckServiceGroup)
  2. Add your service to the group β†’ reference the InventoryCheckService class
  3. Set the service method to checkAvailability
  4. Set Auto Deploy to true on the service group

Once deployed, the endpoint is available at:

POST https://your-environment.operations.dynamics.com/api/services/InventoryCheckServiceGroup/InventoryCheckService/checkAvailability

Request body:

{
  "request": {
    "ItemId": "D0001",
    "WarehouseId": "WH-MAIN"
  }
}
Question

What three components make up a custom service in F&O?

Click or press Enter to reveal answer

Answer

1) Data contracts β€” classes with DataContractAttribute/DataMemberAttribute defining request and response shapes. 2) Service class β€” contains the business logic methods. 3) Service group β€” the deployment unit that exposes the service as an HTTP endpoint (both REST and SOAP).

Click to flip back

Exam tip: Service group auto-deploy

For the exam, remember that setting Auto Deploy = true on a service group means it is automatically available after deployment β€” no manual activation needed. If Auto Deploy is false, an admin must manually enable it in the Service groups form. The exam may present a scenario where a custom service β€œisn’t working” and the fix is enabling auto-deploy or manual activation.

Consuming External REST APIs from X++

When F&O needs to call an external API β€” a tax service, an address validation API, a shipping provider β€” you use .NET’s HttpClient via X++ interop.

Basic GET request

using System.Net.Http;
using System.Net.Http.Headers;

class ExternalApiCaller
{
    public static str callExternalGet(str _url, str _bearerToken)
    {
        HttpClient          httpClient = new HttpClient();
        HttpResponseMessage response;
        str                 responseBody;

        httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", _bearerToken);

        response = httpClient.GetAsync(_url).Result;

        if (response.IsSuccessStatusCode)
        {
            responseBody = response.Content.ReadAsStringAsync().Result;
        }
        else
        {
            throw error(strFmt("API call failed: %1 %2",
                response.StatusCode, response.ReasonPhrase));
        }

        httpClient.Dispose();
        return responseBody;
    }
}

POST request with JSON body

using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

public static str callExternalPost(str _url, str _bearerToken, str _jsonBody)
{
    HttpClient          httpClient = new HttpClient();
    HttpResponseMessage response;
    StringContent       content;
    str                 responseBody;

    httpClient.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", _bearerToken);

    content = new StringContent(_jsonBody, Encoding::UTF8, "application/json");
    response = httpClient.PostAsync(_url, content).Result;

    if (response.IsSuccessStatusCode)
    {
        responseBody = response.Content.ReadAsStringAsync().Result;
    }
    else
    {
        throw error(strFmt("API call failed: %1", response.StatusCode));
    }

    httpClient.Dispose();
    return responseBody;
}

πŸ—οΈ Vik’s caution: β€œTwo things will bite you with external API calls from X++. First, always dispose HttpClient β€” F&O doesn’t garbage-collect .NET objects aggressively in batch jobs. Second, these calls are synchronous and block the thread. If the external API is slow, your X++ logic stalls. For batch scenarios, add timeout handling.”

Exam tip: .NET interop in X++

X++ can use .NET classes via the using statement. The exam may test whether you know that System.Net.Http.HttpClient is the correct class for REST calls (not WebClient, which is obsolete). Also know that async/await doesn’t work the same way in X++ β€” you call .Result to block and get the synchronous result.

Question

How do you consume an external REST API from X++?

Click or press Enter to reveal answer

Answer

Use System.Net.Http.HttpClient via .NET interop. Create an HttpClient instance, set Authorization headers, call GetAsync/PostAsync, read the response with .Result (synchronous blocking), check IsSuccessStatusCode, then read the response body with ReadAsStringAsync().Result. Always Dispose() the HttpClient when done.

Click to flip back

SOAP Services

SOAP (Simple Object Access Protocol) uses XML-based messaging with a formal WSDL contract. While REST has largely replaced SOAP for new integrations, F&O still supports SOAP in two ways:

Custom services over SOAP

Every custom service group is exposed as both REST and SOAP. The SOAP endpoint:

https://your-environment.operations.dynamics.com/soap/services/InventoryCheckServiceGroup

Use the WSDL to generate a client proxy in the consuming application.

Legacy document services

Carried forward from AX 2012, document services provide SOAP-based CRUD operations on entities. These are being replaced by OData but still appear in some older integrations.

REST vs SOAP in F&O
AspectREST (JSON)SOAP (XML)
FormatJSON (lightweight)XML with SOAP envelope (verbose)
ContractImplicit (URL + HTTP methods)Explicit WSDL definition
DiscoveryBrowse OData $metadataDownload WSDL document
Usage in F&OPrimary / recommendedLegacy, still supported
Custom servicesSupportedSupported (same service group)
PerformanceFaster (smaller payload)Slower (XML parsing overhead)
Modern toolingExcellent (Postman, Power Automate)Limited (SoapUI, .NET WCF)

Electronic Reporting (ER)

Electronic Reporting is a configuration-based framework for generating and parsing structured documents β€” invoices, reports, regulatory filings, data files. While primarily used for document generation, it doubles as a powerful integration tool.

ER as an integration tool

  • Outbound: Generate XML, JSON, CSV, or Excel files from F&O data in a specific format required by an external system
  • Inbound: Parse incoming files and map their content to F&O entities
  • No code required β€” configurations are built in a visual designer
  • Versioning β€” ER configurations are versioned and can be imported from Microsoft’s Global Repository

πŸ”— Marcus explaining to Anita: β€œYour bank requires payment files in a very specific XML format that no standard export gives you. Electronic Reporting lets us configure the exact format they need β€” tag names, namespace, element order β€” all without writing a single line of X++.”

Key ER concepts

ConceptDescription
Data modelAbstract representation of business data (entity relationships)
Model mappingConnects the data model to actual F&O data sources (tables, entities)
FormatThe output structure β€” XML schema, JSON structure, CSV layout
Configuration providerWho owns the configuration (Microsoft, ISV, or custom)
Global RepositoryMicrosoft-hosted library of pre-built ER configurations
Question

What is Electronic Reporting (ER) and how is it used for integration?

Click or press Enter to reveal answer

Answer

ER is a configuration-based framework for generating and parsing structured documents (XML, JSON, CSV, Excel). For integration, it generates outbound files in specific formats (e.g., bank payment XML) and parses inbound files β€” all through a visual designer without X++ code. Configurations are versioned and can be shared via Microsoft's Global Repository.

Click to flip back

Batch OData API

The Batch OData API allows external systems to create, monitor, and manage batch jobs in F&O programmatically.

What you can do

  • Create batch jobs and add tasks via OData
  • Monitor batch job status
  • Trigger batch job execution
  • Query batch job history

Why it matters

Integration scenarios often need to trigger processing that runs as batch jobs (e.g., β€œprocess all pending invoices”). Instead of manually running the batch from the UI, an external orchestrator (like Power Automate or Azure Logic Apps) can call the Batch OData API.

POST https://your-environment.operations.dynamics.com/data/BatchJobs

πŸ”— Marcus’s use case: β€œAnita’s middleware pushes product data via DMF, then needs to trigger a price recalculation batch job. Instead of someone clicking β€˜Run’ in F&O, her middleware calls the Batch OData API to create and execute the job automatically. End-to-end automation.”

Exam tip: Batch OData vs business events

Don’t confuse these. The Batch OData API is for externally triggering batch jobs in F&O (inbound control). Business events (Module 25) are for F&O notifying external systems when something happens (outbound notification). They are complementary β€” a business event might tell middleware β€œinvoice posted”, then middleware calls the Batch OData API to trigger a follow-up job.

Putting It Together: Integration Architecture

A real integration often combines multiple APIs:

External System
     β”‚
     β”œβ”€ REST call ──→ Custom Service (validate/lookup)
     β”‚
     β”œβ”€ DMF enqueue ──→ Recurring Import (bulk data)
     β”‚
     β”œβ”€ Batch OData ──→ Trigger processing job
     β”‚
     └─ Subscribes to ──→ Business Event (notifications)

πŸ”— Marcus’s architecture whiteboard: β€œThe best integrations use each API for what it is good at. Custom services for interactive logic, DMF for bulk data, Batch API for triggering jobs, business events for notifications. Trying to do everything through one API is where projects fail.”

Exam Practice

Knowledge Check

A partner needs to expose a custom inventory validation endpoint that accepts an item ID and returns availability with a custom status code. What should they build?

Knowledge Check

Vik needs to call an external tax calculation API from X++ during sales order creation. Which .NET class should he use?

Knowledge Check

A custom service has been deployed but external callers receive a 404 error. The service class and data contracts are correct. What is the most likely cause?

Question

What URL pattern do custom services use in F&O?

Click or press Enter to reveal answer

Answer

REST: https://your-environment.operations.dynamics.com/api/services/ServiceGroupName/ServiceName/OperationName. SOAP: https://your-environment.operations.dynamics.com/soap/services/ServiceGroupName. Both endpoints are automatically created when a service group is deployed.

Click to flip back

Question

What is the Batch OData API used for?

Click or press Enter to reveal answer

Answer

The Batch OData API allows external systems to create, monitor, trigger, and manage batch jobs in F&O programmatically. It enables end-to-end automation β€” for example, after importing data via DMF, an orchestrator can trigger a batch processing job via this API without manual intervention.

Click to flip back


Next up: Dual-Write & Virtual Entities β€” real-time bidirectional sync between F&O and Dataverse, virtual entities for read-without-copy, and Power Platform integration patterns.