---
source_url: https://itmustbecode.com/dataverse-custom-api-function-get-vs-action-post/
title: Dataverse Custom API: Function (GET) vs Action (POST)
date: 2021-03-25T21:45:30+00:00
categories:
  - blog
tags:
  - dataverse-customapi
  - power-automate
word_count: 1488
reading_time_minutes: 7
type: posts
---

The new **Dataverse Custom API** feature ( _now in General Availability_ 🚀) empowers developers with the ability to create their own **custom messages** to extends the capabilities of the **Power Platform.** These custom messages are then exposed by the Dataverse endpoints (WebApi and Organizationservice) like other out-of-the-box messages (Create, Update, etc..).

While the **Custom API model** shares a lot of similarities with the traditional **Custom Action model** that is available since Dynamics 2013, it also exposes a bunch of **unique features** that are worth taking a look at.

So to continue on my series of posts on **Dataverse Custom API's** unique features, this one will explore the **IsFunction** flag that can be found on the Custom API Table. We will try to understand how this attribute affects the way end-users interact with Custom APIs.

> Please, refer to Microsoft's [official documentation](https://docs.microsoft.com/en-us/powerapps/developer/data-platform/custom-api?WT.mc_id=DX-MVP-5004959) for the most up-to-date information.

## Setting the IsFunction Attribute

When you create a new Dataverse Custom API record, you will notice that you are asked to set a boolean (yes/no) attribute called **IsFunction**. Setting this attribute will have a deep impact on how your custom message will be **exposed** and **consumed** so it's very important to understand the intricacies associated with this choice.

The example below shows how to set the attribute using the [**Custom API Manager**](https://www.xrmtoolbox.com/plugins/XTB.CustomApiManager/) for XrmToolBox. Be careful, setting this attribute is **only available at the creation** and you cannot change it afterward.

{{< figure src="./image-65.png" alt="" caption="" >}}

As to **WHY** someone would set a Custom API as a function or not is really a **philosophical** choice (and a **practical** one as we will see later), the **purpose and internals** of your API stay more or less the same whether you set **IsFunction** to true or false :

1. You have a collection of inputs ( **request parameters**)
1. You execute some code (the **plugin** bound to your Custom API record)
1. You return a collection of outputs ( **response properties**)

{{< figure src="./image-12.png" alt="" caption="" >}}

To further illustrate some of the concepts, we will compare 2 unbound custom APIs that expose exactly the same functionality which is to **retrieve an environment variable** value given an input parameter called ' **Key**'. The only difference will be the **IsFunction** flag.

- **driv\_GetEnvironmentVariable\_FUNCTION**
  - **IsFunction = true** ✔️ -\> **Function**
- **driv\_GetEnviromentVariable**
  - **IsFunction = false** ❌ -\> **Action**

> 👉You can download and install an implementation of the **GetEnviromentVariable** in my collection of Dataverse Custom API [available here](https://github.com/drivardxrm/Dataverse-CustomApis/releases/latest)

## **Function (GET) or Action (POST)**

The **fundamental difference** of setting your API to be a function or an action is found in the way your message will be **exposed** and **consumed** using the Dataverse **REST endpoint** (Web API).

If **IsFunction =** **true** ✔️, your message will be exposed using the **GET** HTTP verb, meaning that any inputs ( **request parameters**) will need to be sent directly in the URL.

{{< figure src="./2021-03-24_18-04-58.png" alt="" caption="" >}}

Whereas, if **IsFunction = false** ❌, your message will be exposed using the **POST** HTTP verb.In that case, the inputs ( **request parameters**) will be sent as a JSON payload in the **body of the request**.

{{< figure src="./2021-03-24_18-12-37.png" alt="" caption="" >}}

Notice that the outputs of the 2 calls are identical. You will receive a collection of **response properties** in JSON format, so **no difference** on that end.

The difference between **GET** and **POST** can look trivial for this simple example having only one input parameter of type string, but when you have several inputs with different types (ex. dates, entity references etc...) I find that the **GET** notation can become a bit **messy**. For me the JSON Body notation of the **POST** is much easier to understand.

## **OK! So why choose one over the other?**

That is to say, if you are a ' **purist**' and you want to **RESpecT the REST** conventions, if your API **doesn't make any change** to the Database(verse) your message might be better expressed as a **function** ( **GET**), and if your message implies any mutation to the data you might want to consider an **action** ( **POST**).

But like I said earlier it's a philosophical choice and I will show you why I think that at this point in time, it's much better to **always** define your Custom API as actions ( **POST**) and leave the **IsFunction** attribute to **false**.

## **Calling Functions and Actions in Power Automate**

I think that **Power Automate** is a place where the Custom API model shines the most. It can provide flow makers with robust ( _hopefully tested_ 😏) and easy-to-use functions that encapsulate complex business logic.

One **major plus** 🌟 for **actions APIs (POST)**, is that they can be called in **Power Automate** using the ‘ **Perform Action**’ step of the **Common Data service (current environment)** connector. Unfortunately ( _at the time of writing_) **Functions APIs (GET)** are not surfaced using this connector.

{{< figure src="./image-5.png" alt="" caption="" >}}

Using the **Perform Action** step, any **Actions APIs (POST)** existing in your environment will be **surfaced** in the **Action Name** dropdown. Upon selection, **input parameters** will be dynamically listed and available for edition.

{{< figure src="./image-6.png" alt="" caption="" >}}

Also, all individual outputs of your API will be **directly** and easily accessible in the **Dynamic content** tab for subsequent use. The connector **automatically parses** the JSON output for you, _ain't that amazing_ 👏.

{{< figure src="./image-10.png" alt="" caption="" >}}

{{< figure src="./image-11.png" alt="" caption="" >}}

> So, **for that reason** alone, I tend to set the **IsFunction** flag to ' **false**' on my Custom APIs just so they can be used easily and without a fuss in **Power Automate**.

**If you really** want to call a function API in **Power Automate** (or if you want to use one of the many **out-of-the-box** functions 👉 [**see the list here**](https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/functions?view=dynamics-ce-odata-9?WT.mc_id=DX-MVP-5004959)), you can always use the **HTTP with Azure AD** connector and send the same kind of request seen earlier in the PostMan example for the function API ( **GET**). The API code will be executed and the results ( **response properties**) in JSON will be available in the Body section of the Outputs.

{{< figure src="./image-7.png" alt="" caption="" >}}

{{< figure src="./image-9.png" alt="" caption="" >}}

{{< figure src="./2021-03-23_19-41-18.png" alt="" caption="" >}}

Although, this approach is **far less portable** and **more complicated** to use to than using the **Perform Action** step. Here is why :

- If you want to install your Flow in **different environments**, You will need to provide the base URL dynamically ( _by using an environment variable for example_) or change it manually on each environment ( _who wants to do that?_ 🤷‍♂️ )
- You need to know the name and input parameters of your message, no help from the platform here to surface the available messages and parameters. ( _Not very Citizen Developer friendly_)
- Outputs will not be directly available in the dynamics content panel, you will have to parse the JSON output if you want to access the values directly.
- Should I say more... I think you get the idea.

## **Calling Functions and Actions from Plugins**

Interestingly there are **no difference** in the code that would be needed to call a function ( **GET**) or an action ( **POST**) Custom API inside another plugin (or any C# program) using **Microsoft.Xrm.Sdk** objects from [**Microsoft.CrmSdk.CoreAssemblies**](https://www.nuget.org/packages/Microsoft.CrmSdk.CoreAssemblies) Nuget package.

In this case, we are closer to the metal, and using the good'ol **Organizationservice** endpoint. The 2 APIs can be called exactly the same way using the generic **OrganizationRequest** object. The inputs will be set in the ' **Parameters**' collection and any outputs can be retrieved in the ' **Results**' collection of the **OrganizationResponse** object received from the execution of the request.

{{< figure src="./2021-03-24_21-55-00_GET.png" alt="" caption="" >}}

{{< figure src="./2021-03-24_21-55-00_POST.png" alt="" caption="" >}}

So if you design a Custom API and you know for a fact that it will only be called in that manner, there is no harm defining it as a function ( **GET**), **but why limit yourself**? I would still prefer to use a POST API just in case.

## **Conclusion**

So to wrap this up, it's important to understand the effects of the **IsFunction** attribute on **Dataverse Custom APIs**, mainly affecting how APIs are called on the wire (WebApi REST endpoint) with **GET** or **POST** HTTP Verbs.

Also in a **Power Automate** context, the **Perform Action** step of the **Common Data Service (current environment)** connector is a fantastic vehicle for calling **POST** Custom APIs. Providing **API discoverability** as well as a **wrapper** around the inputs and outputs of custom messages.

Happy **API**'ing!

## **Links**
{{< linkcard url="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/custom-api?WT.mc_id=DX-MVP-5004959" title="Create and use Custom APIs (Microsoft Dataverse) - Power Apps" summary="Custom API is a new code-first way to define custom messages for the Microsoft Dataverse." image="/linkcards/f69ad1e3eed32c06.png" domain="learn.microsoft.com" new_tab="true" nofollow="true" >}}

{{< linkcard url="https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/use-web-api-functions?WT.mc_id=DX-MVP-5004959" title="Use Web API functions (Microsoft Dataverse) - Power Apps" summary="Functions are reusable operations that are used with a GET request to retrieve data from Microsoft Dataverse" image="/linkcards/f69ad1e3eed32c06.png" domain="docs.microsoft.com" new_tab="true" nofollow="true" >}}

{{< linkcard url="https://www.xrmtoolbox.com/plugins/XTB.CustomApiManager/" title="Dataverse Custom API Manager · XrmToolBox" summary="Management tool that provides 360° View of Dataverse Custom APIs. Provides CRUD operations on Custom API, Request Parameters (Inputs) and Response Properties (Outputs)." image="/linkcards/7963c972123cfedd.png" domain="www.xrmtoolbox.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://github.com/drivardxrm/Dataverse-CustomApis/releases/latest" title="Release v1.2021.01.28 · drivardxrm/Dataverse-CustomApis" summary="Added API RemoveDiacritics" image="/linkcards/e9ffe6985986dfe4.png" domain="github.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://docs.microsoft.com/en-us/dynamics365/customer-engagement/web-api/functions?WT.mc_id=DX-MVP-5004959" title="functions?WT.mc_id=DX-MVP-5004959" summary="A function is an operation which does not have observable side effects. They typically retrieve data. They may have parameters and they may return values. Functions may be bound to entity types." image="/linkcards/34be76da44fadf0c.png" domain="docs.microsoft.com" new_tab="true" nofollow="true" >}}


{{< linkcard url="https://www.nuget.org/packages/Microsoft.CrmSdk.CoreAssemblies" title="Microsoft.CrmSdk.CoreAssemblies 9.0.2.32" summary="This package contains the official Microsoft.Xrm.Sdk.dll and Microsoft.Crm.Sdk.Proxy.dll assemblies plus tools and has been authored by the Microsoft Common Data Service SDK team." image="/linkcards/eaf7337eebc8369d.png" domain="www.nuget.org" new_tab="true" nofollow="true" >}}

