Technical6 min read

Custom service or OData action in D365 F&O: which to choose?

Expose D365 F&O X++ logic: OData action on a data entity, or custom JSON/SOAP service. Differences, syntax and use cases from Microsoft Learn.

The short answer

To expose X++ logic to an external system, Dynamics 365 Finance & Operations offers two main paths:

  • An OData action: a method added to a public data entity and decorated with SysODataActionAttribute. It appears in the OData $metadata and is called from the service URL. Ideal when the operation concerns an entity that is already exposed.
  • A custom service: an X++ class attached to a service and a service group. Each group is automatically deployed to two endpoints, SOAP and JSON (/api/services/group/service/operation). Suited to operations that match no entity, such as a real-time on-hand inventory lookup, a scenario for which Microsoft recommends a custom service.

Writing an OData action

Microsoft Learn example: the method is added to the entity; the attribute's first argument is the action's public name, the second says whether it is always available.

[SysODataActionAttribute("CalcMaintenanceDuration", true)]
public int CalculateMaintenanceDuration()
{
    //do something
    return 0;
}

To return a typed collection, add SysODataCollectionAttribute (parameter name, or return; X++ type of the items; public resource name):

[SysODataActionAttribute("GetColors", true),
    SysODataCollectionAttribute("return", Types::Record, "CarColor")]
public List GetColorsByAvailability(boolean onlyAvailableVehicles)
{
    List returnList = new List(Types::Record);
    // do something
    return returnList;
}

Writing a custom service

  1. Create an X++ class with public methods; use data contract classes (DataContract) for complex parameters.
  2. Create a Service element pointing to the class and declare its operations.
  3. Add the service to a service group: all groups under the Service group node are deployed automatically.
  4. Call the JSON endpoint https://<host>/api/services/<group>/<service>/<operation> or the SOAP endpoint /soap/services/<group>?wsdl, with Microsoft Entra ID authentication.

For a call from an AI agent, the modern path is different: a class exposed as an AI tool through the Dynamics 365 ERP MCP server. See Copilot Studio agents: what still needs X++.

Common mistakes

  • Creating a custom service for CRUD that an OData data entity already covers.
  • Forgetting to add the service to a group: it is not deployed.
  • Making single OData calls for large volumes: Microsoft directs large volumes to asynchronous APIs (DMF package, recurring integrations).
  • Forgetting security: the entry point (service, entity) must be covered by a privilege linked to the client application's role.
  • Returning a type an OData action does not support: primitive types or a public updatable entity.

Having it generated

OData action, JSON service, contracts and security: short, testable developments. Skalp AI writes the specification, generates the X++ code and unit tests, compiles with a best-practice check, then delivers a pull request to your Azure DevOps: nothing is merged without your technical architect's approval. See D365 F&O API interfaces, the bigger picture in building X++ without a consultant in 2026, and the Basic, Pro and Max plans (1 credit, ≈ €400 on Basic, €200 on Pro, consumed only when the code is generated).

Sources

Frequently asked questions

What is the URL of a custom JSON service?

https://<host>/api/services/<service_group>/<service>/<operation>, according to Microsoft Learn.

Can an OData action return a list?

Yes, with SysODataCollectionAttribute specifying the item type and the public resource name.

OData or custom service for a real-time lookup?

If the data matches an exposed entity, OData. For logic that matches no entity, such as a computed stock lookup, Microsoft recommends a custom service.

Ready to transform your D365?

Join the CIOs who develop their D365 tickets in 2–4 h for 1 credit (≈ €400 on Basic, €200 on Pro).