Since Dynamics CRM 2013 we’ve been able to create our own APIs within the XRM framework using the Custom Action process type. Recently a new option appeared in the maker portal called Custom APIs which sounded very similar, so I thought I’d take a look.

The old: Custom Actions

Custom Actions could be “bound”, which linked them to a particular entity type, or “unbound”, which was a global action. Either type could have additional input or output parameters, and the core operation of the action was defined by the same workflow engine that the standard background workflows used.

Possibly the most important impact of custom actions was that they introduced a new message type that functioned exactly like the standard actions we were used to calling, like Update or QualifyLead. This made them instantly accessible to existing custom Javascript or SDK apps, but also to plugins. Developers could attach plugins to these custom action messages, retrieve the input parameters and set the output parameters.

The transition: Flow

In Unified Interface, references to workflow essentially vanished as we were directed towards Power Automate Flows for automation. However, there was no other replacement for custom actions, the core of which were still defined by the old workflow engine. Change had to come.

The new: Custom APIs

I haven’t seen any documentation yet about custom APIs, but it appears to be a very similar setup to custom actions. They appear in the maker portal in the list of objects you can create in a solution.

From the maker portal we can create a new Custom API object and fill out some key details:

  • The Unique Name – this is the name that will be used for the CDS message to call the API
  • Name, Display Name and Description – all for reference purposes only as far as I can tell
  • Binding Type and Bound Entity Logical Name – similar to custom actions, you can select for the API to be global or bound to a specific entity type. Unlike custom actions you can also choose for the action to be bound to an entity collection, more details on this later
  • Is Function – if this is “No”, the API can be called using a POST Web API request. If it’s “Yes”, it needs to be called with a GET request. Either can also be called via the SDK using an OrganizationRequest.
  • Allowed Custom Processing Step Type – this allows you to restrict what plugin steps can be added to your new message – either none, async-only or both sync and async.
  • Execute Privilege Name – use this to limit who can call your API based on security roles, e.g. “prvCreateAccount” to allow only users that are allowed to create accounts to call the API
  • Plugin Type – defines the plugin that forms the core execution step of this message

Once the Custom API record is created you can add request and response parameters too – each of them requires the unique name that is used to refer to it in code, and the type of value it will hold.

Global & Bound APIs

A global custom action or custom API is one that is not called in the context of any other record. For example, the standard WhoAmI message is entirely standalone. Global actions can take input parameters to give details of records to work on though.

From Web API, a global action is called by making a request to /api/data/v9.0/name. From an SDK app you create and execute a custom OrganizationRequest, giving the name of the action as the message name in the constructor:

var req = new OrganizationRequest("mcd_ValidateAccountReference")
  ["AccountReference"] = "ACC001"
var resp = svc.Execute(req);

A bound action runs in the context of a particular record of a given type. If you create a custom action or API that is bound to accounts, you could call it via Web API using /api/data/v9.0/accounts(guid)/ An SDK app would use the same pattern as for a global action, but supply the reference to the account as the Target parameter:

var req = new OrganizationRequest("mcd_ValidateAccountReference")
  ["Target"] = new EntityReference("account", new Guid("182580da-7ccc-e911-a813-000d3a7ed5a2")),
  ["AccountReference"] = "ACC001"
var resp = svc.Execute(req);

Custom APIs introduce the idea of an API that is bound not to a specific entity, but to an entity collection. This gives the API a query that defines the records it should run on. I haven’t yet managed to invoke this type of API via the Web API, but from an SDK app it looks like:

var req = new OrganizationRequest("mcd_ValidateAccountReference")
  ["InputCollection"] = new QueryExpression("account")
    Criteria = new FilterExpression
      Conditions =
        new ConditionExpression("name", ConditionOperator.Equal, "Data8")
  ["AccountReference"] = "ACC001"
var resp = svc.Execute(req);

Whether it’s bound to a specific entity or a collection, the framework validates that the Target or InputCollection parameters are valid based on the entity type that the API is bound to, so it’s not possible to accidentally call the API with the wrong type.


I’ve created a Custom API called mcd_ParseInt as shown:

with one request parameter:

and one response parameter:

The plugin that is referenced by the API is a very simple class:

public class CustomApiPlugin : IPlugin
  public void Execute(IServiceProvider provider)
    var context = (IPluginExecutionContext) provider.GetService(typeof(IPluginExecutionContext));
    context.OutputParameters["IntOutput"] = Int32.Parse((string) context.InputParameters["StringInput"]);

I can execute my new API via C#:

var req = new OrganizationRequest("mcd_ParseInt")
  ["StringInput"] = "123"
var resp = svc.Execute(req);
var result = (int) resp["IntOutput"];


So how do Custom APIs stack up against Custom Actions?

Custom ActionCustom API
Solution Aware
Input Parameters
Output Parameters
Can attach plugins
Core logic definitionWorkflowPlugin
Global actions
Bound actions
Collection actions
Control over 3rd party plugins
Built-in security
Handles changing parameter definitions

The four differences at the end could be quite significant in particular use cases:

  • we’ve never been able to pass a query to a custom action before – you could have created a string parameter that you put FetchXML into, but this gives callers the choice of what query type to provide
  • being able to control whether third parties can attach plugins to your new message, or whether they can do so synchronously or asynchronously, could be of particular benefit to ISVs looking to limit further customization of key parts of their solutions
  • having a built-in way to control who can access the API via the standard CDS privilege system moves a task that would normally be done in the associated plugin into a declarative configuration setting, making it easier and less error prone to implement
  • errors encountered when developing a new custom action caused by adding or removing parameters was a common blocker to development. This restriction appears to be gone with custom APIs, making it much easier to iterate and refine the definition of APIs early in the development process

3 thoughts on “CDS Custom APIs”

  1. Very nice and thorough article! I’m a bit surprised to see that in this no code/low code era, custom API’s seem to be more code oriented than their Custom Action counterpart. A lot of Custom Actions were really no code solutions because of the workflow designer behind it… Also, it looks like they are not directly executable from within a business process flow, right?

Leave a Reply

Your email address will not be published. Required fields are marked *

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