0% found this document useful (0 votes)
630 views

Dynamics API

This document provides an overview of how to work with data using the Microsoft Dataverse Web API from C# code. It describes using the Web API to perform CRUD operations on table rows as well as functions and actions. The document includes a quick start guide that shows how to authenticate to a Dataverse environment and call the WhoAmI function to retrieve information about the logged in user using a simple console application. It recommends reviewing additional code samples and documentation on performing other common data operations with the Web API.

Uploaded by

Joji Varghese
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
630 views

Dynamics API

This document provides an overview of how to work with data using the Microsoft Dataverse Web API from C# code. It describes using the Web API to perform CRUD operations on table rows as well as functions and actions. The document includes a quick start guide that shows how to authenticate to a Dataverse environment and call the WhoAmI function to retrieve information about the logged in user using a simple console application. It recommends reviewing additional code samples and documentation on performing other common data operations with the Web API.

Uploaded by

Joji Varghese
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 393

Contents

Work with data using code


Use the Web API
Get started using code
(C#)
Get started with using Web API (C#)
Quick Start: Web API sample (C#)
Quick Start: Blazor Server sample (C#)
Enhanced quick start (C#)
Start Web API project in Visual Studio
Security and web service access
Web service authentication
Active Directory group teams
Types and operations
Perform Web API operations
Get started with performing operations
Compose HTTP requests and handle errors
Query Data with Web API
Query data
Retrieve related rows with query
Retrieve and execute predefined queries
Create a table row
Retrieve a table row
Update and delete table rows
Associate and disassociate rows
Merge table rows
Use functions
Use actions
Execute batch operations
Impersonate another user
Perform conditional operations
Detect duplicate data
Access table data faster using partitions
Work with table definitions (metadata)
Use the Web API with table definitions
Query table definitions
Retrieve table definitions by name or MetadataId
Create and update table definitions
Create and update table relationships
Use multi-table lookup columns
Work with choices (option sets)
Use Postman with Web API
Get started with Postman
Setup a Postman environment
Use Postman to perform operations
Client-side JavaScript using Web API in model-driven apps
Web API versions and limitations
Dataverse search, suggestions, and autocomplete
Discover the URL for your organization
Get started
Modify your code to use global Discovery Service
Global Discovery Service Sample (C#)
Samples
(HTTP) samples
About the samples
Basic Operations
Query Data
Conditional Operations
Functions and Actions
(C#) samples
CDSWebApiService class
Basic Operations
Query Data
Conditional Operations
Functions and Actions
Parallel Operations
Async Parallel Operations
Global Discovery Service
(JavaScript) samples
About the client-side samples
Basic Operations
Conditional Operations
Functions and Actions
Query Data
Work with data using code in Microsoft Dataverse
7/19/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Dataverse has tables that are used to model and manage business data. You can use the stock provided tables or
define your own custom tables to store data.

Use web services to work with data


Dataverse provides two web services that you can use to interact with data: data service, and Organization
service. Choose the one that best matches the requirement and your skills. Use the Web API when coding for the
data service and the SDK API when coding for the Organization service.

Web API
The Web API is an OData v4 RESTful endpoint. Use this for any programming language that supports HTTP
requests and authentication using OAuth 2.0.
More information: Use the Dataverse Web API
Organization service
Use the .NET Framework SDK assemblies for projects that involve writing plug-ins or workflow extensions.
More information: Use the Dataverse Organization service
NOTE
Use the Xrm.Tooling assemblies if you are creating a Windows client application. More information: Build Windows client
applications using the XRM tools
Use the Microsoft Dataverse Web API
5/21/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

The Web API is one of two web services you can use to work with data, and table and column definitions in
Dataverse. The other is the Organization Service.
The Dataverse Web API provides a development experience that can be used across a wide variety of
programming languages, platforms, and devices. The Web API implements the OData (Open Data Protocol),
version 4.0, an OASIS standard for building and consuming RESTful APIs over rich data sources. You can learn
more about this protocol at https://www.odata.org/. Details about this standard are available at
https://www.oasis-open.org/standards#odatav4.0.
Because the Web API is built on open standards, we don't provide assemblies for a specific developer
experience. You can compose HTTP requests for specific operations or use third-party libraries to generate
classes for whatever language or platform you want. You can find a list of libraries that support OData version
4.0 at https://www.odata.org/libraries/.

Web API and the Organization service


It is valuable to recognize that the organization service is what defines the platform. The Web API provides a
RESTful programming experience but ultimately all data operations go through the underlying organization
service. The organization service defines the supported operations as messages. Each message has a name.
These names are bound to the events used in the event framework to evaluate what registered extensions
should be initiated. More information: Event Framework
The Web API allows you to do all the same operations as the organization service but presents them in an
RESTful style. OData v4 provides for named operations via functions or actions. Most messages available in the
organization service are exposed as a corresponding named function or action. Those messages that correspond
to CRUD operations are not available in the Web API because as a RESTful service they have implementations
using GET, POST, PATCH, and DELETE HTTP methods, but within the platform the retrieve, create, update, and
delete messages are invoked just as they are when the corresponding operations are performed using the .NET
Framework assemblies.

Getting started
Now that you have read an overview of the Web API, proceed to the Get started with Dataverse Web API topic
to learn how to write your first C# program in Visual Studio that uses the Web API.
If you are a JavaScript developer and want to use the Web API in model-driven apps, navigate to Client-side
JavaScript using Web API in model-driven apps.
Related Sections
Work with data using code
OData - the best way to REST
OData Version 4.0 Part 1: Protocol Plus Errata 02
OData Version 4.0 Part 2: URL Conventions Plus Errata 02
OData Version 4.0 Part 3: Common Schema Definition Language (CSDL) Plus Errata 02
Get started with Microsoft Dataverse Web API (C#)
5/21/2021 • 2 minutes to read • Edit Online

This section shows you how to access the Dataverse Web API using the C# programming language. The first
topic, Quick Start: Web API sample (C#), provides the quickest example of how to accomplish this, as all source
code is simplified and provided in one file. The rest of this section shows you how to create and configure Visual
Studio projects that use the Dataverse Web API to perform common business data operations.
After completing this guide, you will have learned enough to explore the many other business operations that
are supported by the Dataverse Web API. You will also have a better understanding about how the Web API
Samples (C#) are structured.

In this section
Quick Start: Web API sample (Do this first)
Enhanced quick start (Do this next)
Start a Dataverse Web API project in Visual Studio
See also
Perform operations using the Web API
Web API Samples (C#)
Quick Start: Web API sample (C#)
10/1/2021 • 5 minutes to read • Edit Online

In this quick start you will create a simple console application to connect to your target Microsoft Dataverse
environment and invoke the Web API WhoAmI function. This function retrieves information about the logged on
Dataverse user. Once you understand the basic functionality described here, you can move onto other Web API
operations such as create, retrieve, update, and deletion of Dataverse table rows.
This program will authenticate and use an HttpClient to send a GET request to the WhoAmI Function the
response will be a WhoAmIResponse ComplexType. The program will then display the UserId property value
obtained from the response.

NOTE
This is a very simple example to show how to get connected with a minimum of code. The Enhanced quick start will build
upon this sample to apply better design patterns.

You can find the complete Visual Studio solution for this project in the PowerApps-Samples repo under
cds/webapi/C#/QuickStart.

Prerequisites
Visual Studio 2019
Internet connection
Valid user account for a Dataverse environment
Url to the Dataverse environment you want to connect with
Basic understanding of the Visual C# language

NOTE
To authenticate you must have an app registered in Azure Active Directory (AD). This quick start example provides an app
registration clientid value you can use for the purpose of running sample code published by Microsoft. However, for
your own custom applications you must register your apps with AD. More information: Walkthrough: Register an app with
Azure Active Directory

Create Visual Studio project


1. Launch Visual Studio and select Create a new project .
2. Create a new Console App (.NET Framework) project.
3. Configure the project to use .NET Framework 4.6.2.

4. In Solution Explorer , right-click the project you created and select Manage NuGet Packages in the
context menu. We will now bring in required assemblies for our project.
5. Browse for the Microsoft.IdentityModel.Clients.ActiveDirectory NuGet package, select it, and then
choose Install .
NOTE
You will be prompted to preview and OK the assembly additions, and Accept the license agreements, for the
installed packages and their contents.

To use the Microsoft Authentication Library (MSAL) instead of Azure Active Directory Authentication
Library (ADAL), browse for and install the Microsoft.Identity.Client package instead of the
Microsoft.IdentityModel.Clients.ActiveDirectory package.

6. Browse for the Newtonsoft.Json NuGet package and install the latest version.

Edit Program.cs
Follow these next steps to add code for the main program.
1. Replace the entire contents of Program.cs with the following code. If you used a different name for your
project than WebAPIQuickStart, you will need to change to namespace name in the new code to match your
project name.

C#/ADAL
C#/MSAL

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json.Linq;
using System;
using System.Net.Http;
using System.Net.Http.Headers;

namespace WebAPIQuickStart
{
class Program
{
static void Main()
{
// TODO Specify the Dataverse environment URL to connect with.
string resource = "https://<env-name>.<region>.dynamics.com";

// Azure Active Directory app registration shared by all Power App samples.
// For your custom apps, you will need to register them with Azure AD yourself.
// See https://docs.microsoft.com/powerapps/developer/data-platform/walkthrough-register-app-
azure-active-directory
var clientId = "51f81489-12ee-4a9e-aaae-a2591f45987d";
var redirectUri = new Uri("app://58145B91-0C36-4500-8554-080854F2AC97");

#region Authentication

// The authentication context used to acquire the web service access token
var authContext = new AuthenticationContext(
"https://login.microsoftonline.com/common", false);

// Get the web service access token. Its lifetime is about one hour after
// which it must be refreshed. For this simple sample, no refresh is needed.
// See https://docs.microsoft.com/powerapps/developer/data-platform/authenticate-oauth
var token = authContext.AcquireTokenAsync(
resource, clientId, redirectUri,
new PlatformParameters(
PromptBehavior.SelectAccount // Prompt the user for a logon account.
),
UserIdentifier.AnyUser
).Result;
#endregion Authentication

#region Client configuration

var client = new HttpClient


{
// See https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/compose-
http-requests-handle-errors#web-api-url-and-versions
BaseAddress = new Uri(resource + "/api/data/v9.2/"),
Timeout = new TimeSpan(0, 2, 0) // Standard two minute timeout on web service calls.
};

// Default headers for each Web API call.


// See https://docs.microsoft.com/powerapps/developer/data-platform/webapi/compose-http-
requests-handle-errors#http-headers
HttpRequestHeaders headers = client.DefaultRequestHeaders;
headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
headers.Add("OData-MaxVersion", "4.0");
headers.Add("OData-Version", "4.0");
headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
#endregion Client configuration

#region Web API call

// Invoke the Web API 'WhoAmI' unbound function.


// See https://docs.microsoft.com/powerapps/developer/data-platform/webapi/compose-http-
requests-handle-errors
// See https://docs.microsoft.com/powerapps/developer/data-platform/webapi/use-web-api-
functions#unbound-functions
var response = client.GetAsync("WhoAmI").Result;

if (response.IsSuccessStatusCode)
{
// Parse the JSON formatted service response to obtain the user ID.
JObject body = JObject.Parse(
response.Content.ReadAsStringAsync().Result);
Guid userId = (Guid)body["UserId"];

Console.WriteLine("Your user ID is {0}", userId);


}
else
{
Console.WriteLine("Web API call failed");
Console.WriteLine("Reason: " + response.ReasonPhrase);
}
#endregion Web API call

// Pause program execution.


Console.ReadKey();
}
}
}

2. Right below the TODO comment in the above code, replace the resource value with the actual URL of
your Dataverse test environment. To find the URL value for your test environment, follow these steps:
a. Navigate your browser to Power Apps.
b. Select the environments icon (to the right of the search field), and choose a test environment.
c. Select the settings icon and choose Developer resources .
d. Copy the Web API endpoint URL from "https:" through ".com" leaving off the /api/data/v9.x.
e. Replace the resource string value in the program code with that endpoint URL value. For example:
string resource = "https://contoso.api.crm.dynamics.com";

Run the program


1. Press F5 to build and run the program. The output should look something like this:
Your user ID is 969effb0-98ae-478c-b547-53a2968c2e75

2. With the console window active, press any key to terminate the program.
Congratulations!
You have successfully connected to the Web API.
The quick start sample shows a simple approach to create a Visual Studio project without any exception
handling or method to refresh the access token.
This is enough to verify you can connect, but it doesn't necessarily represent a good pattern for building an app.
The Enhanced quick start topic shows how to implement exception handling methods, basic authentication
method using connection string, a re-usable method to refresh the access token, and introduces how to build re-
usable methods to perform data operations.

Next steps
Learn how to structure your code for a better design.
Enhanced quick start
Quickstart: Blazor Server Web API sample (C#)
7/19/2021 • 5 minutes to read • Edit Online

In this quickstart, you'll create a Blazor Server application to connect to your Microsoft Dataverse environment
using the Web API.
You'll authenticate and use HttpClient to send a GET request containing the WhoAmI function. The response will
be a WhoAmIResponse complex type. After call completion, the UserId property value is displayed.

NOTE
This is a very simple example to show how to get connected with a minimum of code. The Enhanced quickstart will build
upon this sample to apply better design patterns.

Prerequisites
Visual Studio 2019 (version 16.6.2 or later recommended)
Familiarity with the Microsoft Azure portal
Internet connection
Valid user account for a Dataverse instance
Administrator access to grant application registrations
URL to the Dataverse environment you want to connect with
Basic understanding of the Visual C# language

NOTE
To authenticate you must have an app registered in Azure Active Directory. The registration will happen automatically as
part of the template creation, but will require additional updates in the Azure portal.

Create a Visual Studio project


1. Create a new Blazor Server app using .NET Core 3.1 but don't choose Create just yet.
2. Select Change under Authentication and then choose Work or School Accounts .

3. Choose the appropriate dropdown and then replace CRM520451 in the example with your environment's
name.
4. Select Create .

Configure the application in Active Directory


By default, the template will create a registered application. Connecting to Dataverse will require additional
permissions. Open the Azure portal and log in with your credentials. Navigate to Active Director y and App
Registrations , and then choose the entry with the same name as your application.
1. Choose Authentication , select (check) Access tokens under Implicit grant , and then click Save .
2. Choose Cer tificates & secrets and then select New client secret .
3. Assign the secret a name (for example, "Blazor Server client") and expiration date, and then select Add .
4. Select the clipboard icon next to your secret to copy it.

5. In your Blazor Server app, open appsettings.json and add an entry for "ClientSecret". The Active
Directory settings should look like this:

{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "{org}.onmicrosoft.com",
"TenantId": "{tenantId}",
"ClientId": "{clientId}",
"ClientSecret": "{secret}",
"CallbackPath": "/signin-oidc"
}
}

6. Navigate to API permissions


7. Select Add a permission and choose Dynamics CRM
8. Choose Delegated permissions and select (check) user_impersonation , and then click Add
permissions
9. Select the newly created permission to highlight it, and then shoose Grant admin consent for
organization (your environment name is shown)
10. Verify the permissions have green checkboxes in the status column

Prepare the app to use Azure AD tokens


The application requires some extra steps to capture the authentication token and pass it to the Web API
request.
1. Right-click on the Data folder and add a new class named TokenProvider .

public class TokenProvider


{
public string AccessToken { get; set; }
}

2. Open the App.razor file and add the following statements to the top of the file. Change the namespace to
match the name of your application.

@using BlazorWebAPIExample.Data
@inject TokenProvider Service

3. Add a @code block to accept a parameter and move the token into the service.

[Parameter]
public TokenProvider InitialState { get; set; }

protected override void OnInitialized()


{
Service.AccessToken = InitialState.AccessToken;
base.OnInitialized();
}

4. Open the Pages/_Host.cshtml file and add the following using statements after the namespace
declaration.
@using BlazorCommonDataService.Data
@using Microsoft.AspNetCore.Authentication

5. After the <body> tag, add the following code and update the app component to acquire and pass the
token.

@{
var token = new TokenProvider
{
AccessToken = await HttpContext.GetTokenAsync("access_token")
};
}
<app>
<component type="typeof(App)" param-InitialState="token" render-mode="ServerPrerendered" />
</app>

6. Obtain the environment name for the Dataverse management API. If you're not sure what the name is,
open the Power Platform admin center, navigate to Environments then choose Open environment .
You will see a URL like this: https://{org}.crm.dynamics.com where {org} is the environment name.
7. Add an entry named CDSAPI to the appsettings.json file with the environment URL as the value. Append
/api/data/v9.0/ to the end of the URL so it looks like this:

{ "CDSAPI": "https://{org}.crm.dynamics.com/api/data/v9.0/" }

8. Add this using statement to the file Startup.cs.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

9. In the Startup.cs class, add registrations to retrieve the authentication token and configure a client
ready to use the token. Place this code between services.AddAuthentication and
services.AddControllersWithViews .

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme,
opt =>
{
var resourceUri = new Uri(Configuration["CDSAPI"]);
var resource = $"{resourceUri.Scheme}://{resourceUri.Host}/";
opt.ResponseType = "code";
opt.SaveTokens = true;
opt.Scope.Add("user_impersonation");
opt.Scope.Add(new Uri(Configuration["CDSAPI"]).Host);
opt.Resource = resource;
});
services.AddScoped<TokenProvider>();
services.AddHttpClient("CDS", client =>
{
client.BaseAddress = new Uri(Configuration["CDSAPI"]);
});

The first registration allows requesting the token with the proper scope. The second registers the service that
tracks the token, and the third creates a client with the base API address pre-configured.

Make a call to the Web API


Next, you'll update the Index.razor component to call the Web API.
1. Open the Index.razor file and add these statements to the top:

@using System.Text.Json
@using BlazorWebAPIExample.Data;
@using System.Net.Http.Headers;
@inject IHttpClientFactory Factory
@inject TokenProvider TokenProvider

2. Add this markup after the SurveyPrompt component:

@if (Loading)
{
<div class="alert alert-warning">Loading...</div>
}
@if (Error)
{
<div class="alert alert-danger">@ErrorMessage</div>
}
@if (!Loading && !Error)
{
<div class="alert alert-info">You did it! Your user id is: @UserId</div>
}

3. Finally, add a @code block with the following code:

public bool Loading;


public string ErrorMessage;
public bool Error => !string.IsNullOrWhiteSpace(ErrorMessage);
public string UserId;

protected override async Task OnInitializedAsync()


{
Loading = true;
try
{
var client = Factory.CreateClient("CDS");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", TokenProvider.AccessToken);
var result = await client.GetAsync("WhoAmI");
result.EnsureSuccessStatusCode();
UserId = JsonDocument.Parse(await result.Content.ReadAsStringAsync())
.RootElement.GetProperty("UserId").GetGuid().ToString();
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
}
finally
{
Loading = false;
}
await base.OnInitializedAsync();
}

The application is now ready!

Run the program


Press F5 to run the program. The output should look like this:
Congratulations! You have successfully connected to the Web API.
This quickstart sample shows a simple approach to create a Visual Studio project without any exception
handling or method to refresh the access token. You can expand on the example to perform more complex
operations, and wrap the HttpClient object in a service class to handle the permissions.
The Enhanced quickstart topic shows how to:
Implement exception handling methods
Use basic authentication with a connection string
Create a reusable method to refresh the access token
Build reusable methods for data operations

Next steps
Learn how to structure your code for a better design.
Enhanced quickstart
See Also
Tutorial: Create an ASP.NET Core Blazor WebAssembly App using Dataverse
Enhanced quick start
10/1/2021 • 9 minutes to read • Edit Online

This topic demonstrates how to re-factor the code in the Quick start topic by adding re-usable HttpClient and
error handling methods. Complete the steps in the Quick start topic to create a new Visual Studio project before
you begin this topic, or simply download the MSAL version of the complete Visual Studio project.
If you get stuck following this enhanced quick start, you can download the completed solution.

Enable passing credentials in a connection string


Putting user logon credentials inside your code is not a good practice. How you capture user credentials
depends on the type of client you are making. For this console application we will set the credentials within the
App.config file because it is a convenient way to move the credentials out of code. It is also the method used in
the Web API Data operations Samples (C#), so if you understand this method, you can easily see how the other
Web API samples work.
Enabling this requires three steps:
1. Add a System.Configuration reference to the Visual Studio project
2. Edit the application configuration file
3. Add a using directive in Program.cs
Add a System.Configuration reference to the Visual Studio project
1. In Solution Explorer , right click References and select Add Reference... .
2. In the Reference Manager dialog search for System.Configuration and select the checkbox to add this
reference to your project.
3. Click OK to close the Reference Manager dialog.
Edit the application configuration file
In Solution Explorer , open the App.config file. It should look something like this:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
</configuration>

Edit the <configuration> element to add a the connectionStrings node as shown below:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
<connectionStrings>
<!--Online using Microsoft 365-->
<add name="Connect"

connectionString="Url=https://yourorg.api.crm.dynamics.com;[email protected];Passwor
d=mypassword;" />
</connectionStrings>
</configuration>

This creates a connection string that can be referenced by name, in this case Connect , so that you can define
more than one connection if you wish.
Edit the connection string Url , Username and Password values in the connectionString parameter to match
what you need to connect to your Microsoft Dataverse test environment.
Add using directive to Program.cs
At the top of your Program.cs file, add this using directive:

using System.Configuration;

Install the MSAL NuGet package


In Visual Studio, right-click the project in Solution Explorer and choose Manage NuGet Packages in the
context menu. Browse for and install the Microsoft.Identity.Client package into your project. This package will be
used for web service authentication.

Add helper code


In the Quick start example, all the code is within the Program.cs file. We are going to move the code that deals
with connecting and creating an HttpClient into a separate file of helper methods.
These helpers are also used in the SampleHelper.cs file used by the Web API Data operations Samples (C#). If
you understand these helpers, you will understand how they are used in the samples.
1. In Solution Explorer , right click your project and select Add > Class... (or press Shift + Alt + C ) to
open the Add New Item dialog.
2. Specify a name for your class file. To follow the pattern used by the Web API Data operations Samples
(C#), call it "SampleHelpers.cs".

NOTE
The name of the class will determine how you will reference these helper properties and methods within your main
program. The remaining instructions will assume you named the class SampleHelpers .

3. Add the following code to your SampleHelpers.cs file.

using System;
using System.Linq;
using System.Net.Http;
namespace EnhancedQuickStart
{
/// <summary>
/// Shared code for common operations used by many Power Apps samples.
/// </summary>
class SampleHelpers
{
//These sample application registration values are available for all online instances.
//You can use these while running sample code, but you should get your own for your own apps
public static string clientId = "51f81489-12ee-4a9e-aaae-a2591f45987d";
public static string redirectUrl = "app://58145B91-0C36-4500-8554-080854F2AC97";

/// <summary>
/// Method used to get a value from the connection string
/// </summary>
/// <param name="connectionString"></param>
/// <param name="parameter"></param>
/// <returns>The value from the connection string that matches the parameter key value</returns>
public static string GetParameterValueFromConnectionString(string connectionString, string parameter)
{
try
{
return connectionString.Split(';').Where(s =>
s.Trim().StartsWith(parameter)).FirstOrDefault().Split('=')[1];
}
catch (Exception)
{
return string.Empty;
}
}

/// <summary>
/// Returns an HttpClient configured with an OAuthMessageHandler
/// </summary>
/// <param name="connectionString">The connection string to use.</param>
/// <param name="clientId">The client id to use when authenticating.</param>
/// <param name="redirectUrl">The redirect Url to use when authenticating</param>
/// <param name="version">The version of Web API to use. Defaults to version 9.2 </param>
/// <returns>An HttpClient you can use to perform authenticated operations with the Web API</returns>
public static HttpClient GetHttpClient(string connectionString, string clientId, string redirectUrl,
string version = "v9.2")
{
string url = GetParameterValueFromConnectionString(connectionString, "Url");
string username = GetParameterValueFromConnectionString(connectionString, "Username");
string password = GetParameterValueFromConnectionString(connectionString, "Password");
try
{
HttpMessageHandler messageHandler = new OAuthMessageHandler(url, clientId, redirectUrl,
username, password,
new HttpClientHandler());

HttpClient httpClient = new HttpClient(messageHandler)


{
BaseAddress = new Uri(string.Format("{0}/api/data/{1}/", url, version)),

Timeout = new TimeSpan(0, 2, 0) //2 minutes


};

return httpClient;
}
catch (Exception)
{
throw;
}
}

/// <summary> Displays exception information to the console. </summary>


/// <param name="ex">The exception to output</param>
/// <param name="ex">The exception to output</param>
public static void DisplayException(Exception ex)
{
Console.WriteLine("The application terminated with an error.");
Console.WriteLine(ex.Message);
while (ex.InnerException != null)
{
Console.WriteLine("\t* {0}", ex.InnerException.Message);
ex = ex.InnerException;
}
}
}
}

4. Add an OAuthMessageHandler class in its own class file using the code provided below.

This class ensures that the access token is refreshed each time an operation is performed. Each access token will
expire after about an hour. This class implements a DelegatingHandler that will work with the Microsoft
Authentication Library (MSAL) authentication context to call the correct AcquireToken variation every time an
operation is performed so you don't need to explicitly manage token expiration.
using Microsoft.Identity.Client;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Threading.Tasks;

namespace EnhancedQuickStart
{
/// <summary>
/// Custom HTTP message handler that uses OAuth authentication through
/// Microsoft Authentication Library (MSAL).
/// </summary>
class OAuthMessageHandler : DelegatingHandler
{
private AuthenticationHeaderValue authHeader;

public OAuthMessageHandler(string serviceUrl, string clientId, string redirectUrl, string username,


string password,
HttpMessageHandler innerHandler)
: base(innerHandler)
{

string apiVersion = "9.2";


string webApiUrl = $"{serviceUrl}/api/data/v{apiVersion}/";

//Build Microsoft.Identity.Client (MSAL) OAuth Token Request


var authBuilder = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.WithRedirectUri(redirectUrl)
.Build();
var scope = serviceUrl + "//.default";
string[] scopes = { scope };

AuthenticationResult authBuilderResult;
if (username != string.Empty && password != string.Empty)
{
//Make silent Microsoft.Identity.Client (MSAL) OAuth Token Request
var securePassword = new SecureString();
foreach (char ch in password) securePassword.AppendChar(ch);
authBuilderResult = authBuilder.AcquireTokenByUsernamePassword(scopes, username,
securePassword)
.ExecuteAsync().Result;
}
else
{
//Popup authentication dialog box to get token
authBuilderResult = authBuilder.AcquireTokenInteractive(scopes)
.ExecuteAsync().Result;
}

//Note that an Azure AD access token has finite lifetime, default expiration is 60 minutes.
authHeader = new AuthenticationHeaderValue("Bearer", authBuilderResult.AccessToken);
}

protected override Task<HttpResponseMessage> SendAsync(


HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Authorization = authHeader;
return base.SendAsync(request, cancellationToken);
}
}
}

Update Program.cs
Now that you have made the changes to Enable passing credentials in a connection string and Add helper code,
you can update the Main method in the Program.cs file to only contain the following:

using Newtonsoft.Json.Linq;
using System;
using System.Configuration;
using System.Net.Http;

namespace EnhancedQuickStart
{
class Program
{
static void Main(string[] args)
{
try
{
//Get configuration data from App.config connectionStrings
string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;

using (HttpClient client = SampleHelpers.GetHttpClient(connectionString,


SampleHelpers.clientId, SampleHelpers.redirectUrl))
{
// Use the WhoAmI function
var response = client.GetAsync("WhoAmI").Result;

if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
Guid userId = (Guid)body["UserId"];
Console.WriteLine("Your UserId is {0}", userId);
}
else
{
Console.WriteLine("The request failed with a status of '{0}'",
response.ReasonPhrase);
}
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
catch (Exception ex)
{
SampleHelpers.DisplayException(ex);
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
}
}

This is less code and you have added error handling and the means to refresh the access token with every use of
the HttpClient .
Press F5 to run the program. Just like the Quick start sample, the output should look like this:

Your UserId is 969effb0-98ae-478c-b547-53a2968c2e75


Press any key to exit.

Create re-usable methods


While we have reduced the total amount of code in the Program.Main method, you aren't going to write a
program to just call one operation, and it isn't realistic to write so much code just to call a single operation.
This section shows how you can change this:

var response = client.GetAsync("WhoAmI").Result;

if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
Guid userId = (Guid)body["UserId"];
Console.WriteLine("Your UserId is {0}", userId);
}
else
{
Console.WriteLine("The request failed with a status of '{0}'",
response.ReasonPhrase);
}

to this:

WhoAmIResponse response = WhoAmI(client);


Console.WriteLine("Your system user ID is: {0}", response.UserId);

Before you begin, it would be a good idea to go out to the Web API Reference and review these topics:
WhoAmI Function
WhoAmIResponse ComplexType
Notice how the WhoAmI Function returns a WhoAmIResponse ComplexType and the WhoAmIResponse
ComplexType contains three GUID properties: BusinessUnitId , UserId , and OrganizationId ;
The code we will add is simply to model these into a re-usable method that accepts an HttpClient as a
parameter.

NOTE
Exactly how you do this is a matter of personal preference. This design is provided because of it's relative simplicity.

In your Visual Studio project perform the following steps:


1. Edit the Program class to make it a partial class.
At the top, change this:
class Program

To this:
partial class Program

2. Create a new class file named ProgramMethods.cs

In ProgramMethods.cs , change this:


class ProgramMethods

To this:
partial class Program
In this way the Program class in ProgramMethods.cs file is just an extension of the original Program class
in the Program.cs file.
3. Add the following using directives to the top of the ProgramMethods.cs file.

using Newtonsoft.Json.Linq;
using System;
using System.Net.Http;

4. Add the following method to the Program class in the ProgramMethods.cs file.

public static WhoAmIResponse WhoAmI(HttpClient client) {


WhoAmIResponse returnValue = new WhoAmIResponse();
//Send the WhoAmI request to the Web API using a GET request.
HttpResponseMessage response = client.GetAsync("WhoAmI",
HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
returnValue.BusinessUnitId = (Guid)body["BusinessUnitId"];
returnValue.UserId = (Guid)body["UserId"];
returnValue.OrganizationId = (Guid)body["OrganizationId"];
}
else
{
throw new Exception(string.Format("The WhoAmI request failed with a status of '{0}'",
response.ReasonPhrase));
}
return returnValue;
}

5. Add the following class outside of the Program class but within the namespace of the
ProgramMethods.cs file.

public class WhoAmIResponse


{
public Guid BusinessUnitId { get; set; }
public Guid UserId { get; set; }
public Guid OrganizationId { get; set; }
}

6. In the Program.Main method in the original Program.cs file:


Replace this:

var response = client.GetAsync("WhoAmI").Result;

if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
Guid userId = (Guid)body["UserId"];
Console.WriteLine("Your UserId is {0}", userId);
}
else
{
Console.WriteLine("The request failed with a status of '{0}'",
response.ReasonPhrase);
}
With this:

WhoAmIResponse response = WhoAmI(client);


Console.WriteLine("Your system user ID is: {0}", response.UserId);

7. Press F5 to run the sample and you should get the same results as before.

Troubleshooting
If you have any trouble running this sample, you can download all the Power Apps samples from the GitHub
repository at https://github.com/Microsoft/PowerApps-Samples.

IMPORTANT
All the samples on the GitHub repo are configured to use a common App.config that is located at PowerApps-
Samples:cds\App.config. When you set your connection string you must edit this file. When you do, you can run all the
samples without setting your credentials again.

Create a Template project


Before you leave this topic, consider saving your project as a project template. You can then reuse that template
for future learning projects and save yourself some time and effort in setting up new projects. To do this, while
your project is open in Microsoft Visual Studio, in the File menu select Expor t template . Follow the Export
Template Wizard instructions to create the template.

Next steps
Use the following resources to learn more:
Perform operations using the Web API

Try Web API Data operations Samples (C#)

Review Web API samples (C#) on GitHub


Start a Microsoft Dataverse Web API project in
Visual Studio (C#)
7/19/2021 • 3 minutes to read • Edit Online

This topic demonstrates how to create a new project in Visual Studio 2017 (or later) that builds a console
application that uses the Dataverse Web API. It illustrates the common references and project resources that
most applications, including the SDK C# samples, use to implement Web API-based solutions.

Prerequisites
The following prerequisites are required to build the console application described in this section.
Visual Studio 2017 installed on your development computer. Any edition, including Visual Studio Express,
should be sufficient to work with the Dataverse Web API.
A NuGet client must be installed: either the command-line utility or the Visual Studio extension. For more
information, see Installing NuGet.

Create a project
The following procedure demonstrates how to create a console application project in C# that uses the Microsoft
.NET Framework.

New Project
1. In Visual Studio, click New Project . The New Project dialog is displayed.
2. In the left navigation pane under Templates , select Visual C# .
3. Above the list of available templates, select .NET Framework 4.6.2 .
4. From the list of templates, select Console App(.NET Framework) . (Alternately choose the project type
suited to your solution.) All of the Web API C# samples are console applications.
5. In the text boxes near the bottom of the form, supply the project name and location, and then select OK.
(For this topic, the solution name "StartWebAPI-CS" was used.) The initial solution files will be generated
and the solution loaded into Visual Studio.
6. Under the Project menu, open the project's properties form and verify the target framework is set to
.NET Framework 4.6.2 .
Install and verify the required assembly references
1. After the project opens, click on Tools in the control bar on the top of your project. Select NuGet Package
Manager > Package Manager Console and install the following NuGet packages.

install-package Newtonsoft.Json
install-package System.Net.Http

2. In Solution Explorer , expand the References node.


3. Confirm that all the required references have been added to the project.
4. If you have additional functionality that you routinely use in your applications, you can add the associated
references to the required assemblies now. For more information, see How to: Add or Remove References
by Using the Add Reference Dialog Box.
Because the Dataverse Web API is based on REST principles, it does not require client-side assemblies to
access. However, other APIs supported by Dataverse apps do require these.
Add typical using statements
1. In the Solution Explorer , open Program.cs for editing.
2. At the top of the file, add the following using statements, which reference namespaces commonly used
in Dataverse Web API-based solutions.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Net.Http.Headers;

3. If you added routinely used assemblies or references in the previous sections, you may also want to add
corresponding using statements for these resources.
4. Save the file.

Add connection code


This section explains how to add a basic set of settings and instructions to perform these operations.
Edit the application configuration file
1. In Solution Explorer , open the App.config file for editing. Add the following two sections to it, after the
existing <startup> section, then save the file.

<connectionStrings>
<clear />

<!-- When providing a password, make sure to set the app.config file's security so that only you
can read it. -->
<add name="default" connectionString="Url=https://myserver/myorg/; Username=name;
Password=password; Domain=domain" />
<add name="CrmOnline" connectionString="Url=https://mydomain.crm.dynamics.com/;
[email protected]; Password=password" />
</connectionStrings>

<appSettings>
<!--For information on how to register an app and obtain the ClientId and RedirectUrl
values see https://msdn.microsoft.com/dynamics/crm/mt149065 -->
<!--Active Directory application registration. -->
<!--These are dummy values and should be replaced with your actual app registration values.-->
<add key="ClientId" value="e5cf0024-a66a-4f16-85ce-99ba97a24bb2" />
<add key="RedirectUrl" value="https://localhost/SdkSample" />

<!-- Use an alternate configuration file for connection string and setting values. This optional
setting
enables use of an app.config file shared among multiple applications. If the specified file does
not exist, this setting is ignored.-->
<add key="AlternateConfig" value="C:\Temp\crmsample.exe.config"/>
</appSettings>

2. When developing or deploying a solution, the actual connection and application registration values must
be substituted for the example placeholder values.
Next steps
At this point the solution can be built without errors. If you edit the application configuration file to supply
values for your Dynamics 365 Server, the program should also successfully connect to that server. The solution
represents a skeletal frame that is ready to accept custom code, including calls to the Dataverse Web API.

TIP
Before you leave this topic, consider saving your project as a project template. You can then reuse that template for future
learning projects and save yourself some time and effort in setting up new projects. To do this, while your project is open
in Microsoft Visual Studio, in the File menu select Expor t template . Follow the Export Template Wizard instructions to
create the template.

See also
Get Started with the Web API (C#)
Use the Web API Helper Library (C#)
Perform operations using the Web API
Authenticate to Microsoft Dataverse with the Web
API
4/30/2021 • 2 minutes to read • Edit Online

You must use OAuth as described in Use OAuth with Dataverse.


The code you write to manage authentication when using the Web API depends on the type of deployment and
where your code is.

Authenticate with JavaScript in web resources


When you use the Web API with JavaScript within HTML web resources, form scripts, or ribbon commands you
don’t need to include any code for authentication. In each of these cases the user is already authenticated by the
application and authentication is managed by the application.
If you’re creating a single page application (SPA) using JavaScript you can use the adal.js library as described in
Use OAuth with Cross-Origin Resource Sharing to connect a Single Page Application.
See also
Use the Dataverse Web API
Web API types and operations
Perform operations using the Web API
Use OAuth with Dataverse
Use OAuth with Cross-Origin Resource Sharing to connect a Single Page Application
Work with Azure Active Directory group teams
9/15/2021 • 2 minutes to read • Edit Online

An Azure Active Directory (AAD) group team, similar to an owner team, can own records and can have security
roles assigned to the team. To read more about AAD group teams see Manage group teams.
The following sections describe how to work with AAD group teams using the Web API.

Create an AAD group team


Citizen developers wanting to programmatically create a Microsoft Dataverse AAD group team can do so by
providing the object ID of an existing AAD group as shown in the following command.
Request

POST [Organization URI]/api/data/v9.0/teams


Accept: application/json

{
"azureactivedirectoryobjectid":"<group object ID>",
"membershiptype":0
}

Where:
Membership type is defined in the team property membershiptype
Name of the team is the name of the AAD group
Team type is based on the AAD group type - for example "Security" or "Microsoft 365"

Assign a security role to an AAD group team


An administrator can assign a security role to an AAD group team after the AAD group team is created.
Request

POST [Organization URI]/api/data/v9.0/teams(azureactivedirectoryobjectid=<group team


ID>,membershiptype=0)/teamroles_association/$ref
Accept: application/json

{
"@odata.id":"[Organization URI]/api/data/v9.0/roles(<role ID>)"
}

Assign a security role to a user


An administrator can assign a security role to an AAD group user. The user is added into Dataverse automatically
if the user doesn’t exist in Dataverse and the role is assigned directly to the user.
Request
POST [Organization URI]/api/data/v9.0/systemusers(azureactivedirectoryobjectid=<user object
ID>)/systemuserroles_association/$ref
Accept: application/json

{
"@odata.id":"[Organization URI]/api/data/v9.0/roles(<role ID>)"
}

Assign a record to an AAD group member


An administrator can assign a record to an AAD group member. The AAD group member is added into
Dataverse automatically if the user doesn’t exist in Dataverse.
The example below shows the syntax for assigning an account record.
Request

PATCH [Organization URI]/api/data/v9.0/accounts(<account ID>)


Accept: application/json

{
"[email protected]": "[Organization URI]/api/data/v9.0/systemusers(azureactivedirectoryobjectid=<user
object ID>)"
}

Security roles and privileges


Members of an AAD group can query all the security roles that are directly and indirectly assigned to them
using the following command.
Request

GET [Organization URI]/api/data/v9.0/RetrieveAadUserRoles(DirectoryObjectId=<user object ID)?


$select=_parentrootroleid_value,name

Response

{
"@odata.context": "https://contoso.crm2.dynamics.com/api/data/v9.0/$metadata#roles",
"value": [
{
"@odata.etag": "W/\"1649865\"",
"name": "System Administrator",
"roleid": "ae0daa93-e566-eb11-bb2b-000d3ac4c3f6",
"_parentrootroleid_value": "ae0daa93-e566-eb11-bb2b-000d3ac4c3f6",
"t_x002e_azureactivedirectoryobjectid": "e1341054-98ed-489b-a522-15e9e277b737",
"t_x002e_membershiptype": 0,
"t_x002e_teamid": "26e477f8-3f6a-eb11-bb2b-000d3af6caae",
"t_x002e_name": "testgroup"
}
]
}

Members of an AAD group can check their security privileges without being a user of Dataverse using the
following command.
Request
GET [Organization URI]/api/data/v9.0/RetrieveAadUserPrivileges(DirectoryObjectId=<user object ID>)

Response

{
"@odata.context":
"https://contoso.crm2.dynamics.com/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.RetrieveAadUserPrivilegesR
esponse",
"RolePrivileges": [
{
"Depth": "Global",
"PrivilegeId": "0a620778-3e9f-46ec-9766-000624db57ba",
"BusinessUnitId": "aa0daa93-e566-eb11-bb2b-000d3ac4c3f6",
"PrivilegeName": "prvDeleteHierarchyRule"
},

]
}

See also
Manage app and resource access using Azure Active Directory groups
Web API types and operations
4/30/2021 • 16 minutes to read • Edit Online

In order to use the Web API you need to find information about what is available for you to use. The service
describes itself via service and metadata documents that you can access. This topic will introduce important
concepts and describe how you can find the information you need using documentation generated from the
service and metadata documents as well as the documentation of the system entity types, functions, and actions.

Terminology
The Web API is implemented using the OData v4 standard which uses a specific set of terms you need to be
familiar with. Entity Data Model (EDM) is the abstract data model that is used to describe the data exposed by an
OData service. The following table is a selected list of terms defined in OData Version 4.0 Part 1: Protocol Plus
Errata 02 which you should understand.

T ERM DEF IN IT IO N

Entity types Named structured types with a key. They define the named
properties and relationships of an entity. Entity types may
derive by single inheritance from other entity types.

Entities Instances of entity types (e.g. account, opportunity).

Entity sets Named collections of entities (e.g. accounts is an entity set


containing account entities). An entity's key uniquely
identifies the entity within an entity set

Complex types Keyless named structured types consisting of a set of


properties. Complex types are commonly used as property
values in model entities, or as parameters or return values
for operations.

Enumeration types or Enum types Named primitive types whose values are named constants
with underlying integer values.

Functions Operations that do not have side effects and may support
further composition, for example, with additional filter
operations, functions or an action

Actions Operations that allow side effects, such as data modification,


and cannot be further composed in order to avoid non-
deterministic behavior

Service documents
There are two service documents you can reference to learn more about the Web API.

Service document
The following query, typed into the address field of your browser, returns the service document, a complete list
of all the entity sets that are available for your organization. Note that [organization URI] represents the URL for
your organization.

[Organization URI]/api/data/v9.0

The entity sets are returned in the form of a JSON array. Each item in the array has three properties as listed in
this table.

P RO P ERT Y DESC RIP T IO N

name This is the name of the entity set. This data is from the
EntityMetadata EntityType/ EntitySetName property for
the entity.

kind For the web API only Entity sets are listed.

url This value is the same as the name property and it


represents the part of the resource path to retrieve data for
the entity.

This information can be retrieved using a GET request and may be useful to get a list of all entity sets available
using the service.

CSDL $metadata document


A GET request to the following URL will return a rather large (more than 3.5 MB) Common Schema Definition
Language (CSDL) document, or metadata document that describes the data and operations exposed by the
service.

GET [Organization URI]/api/data/v9.0/$metadata

This document can be used as a data source to generate classes that will provide strongly typed objects for the
service. But if you are not using generated classes, you may want to review documentation generated from this
information instead. The Web API Reference uses information primarily from this document taken from a base
environment with some common additional solutions installed.
You can learn more about this document in OData Version 4.0 Part 3: Common Schema Definition Language
(CSDL) Plus Errata 02.

TIP
Before your read the rest of this topic, download the $metadata for your organization and take a look at how the types,
functions, and actions described are included in the $metadata and the supporting documentation.
You can view or download the document using your browser by navigating to the URL.

Metadata annotations
The metadata document includes several types of Annotation elements which provide additional information
about metadata elements and the capabilities of the service. Starting with v9.x, these annotations are not
included with the default metadata document unless explicitly requested. These annotations increase the size of
the metadata document and are not always necessary.
To include annotations you have two options when requesting the metadata document:
Append the ?annotations=true query string parameter to the URL.
Add the Prefer: odata.include-annotations="*" header to the request.
Each Annotation element includes a Term attribute that describes the type of annotation. The definitions of all
the possible annotations used by OData v4 can be found in OData Vocabularies Version 4.0. The following table
provides some examples used by the Web API.

T ERM DESC RIP T IO N

Org.OData.Core.V1.Description A description of an entity type or property.

Org.OData.Core.V1.Permissions A list of permissions available for a property when


permissions are restricted. Typically this will appear for
properties with only a single
<EnumMember>Org.OData.Core.V1.PermissionType/Read</EnumMember>
child element to indicate that the property is read-only.

Org.OData.Core.V1.Computed Whether the property is computed. These are also usually


read-only.

OData.Community.Keys.V1.AlternateKeys Contains a collection of elements that describe any alternate


keys defined for an entity. More information:Alternate keys

Org.OData.Capabilities.V1.IndexableByKey Whether an entityset supports key values according to


OData URL conventions.

Org.OData.Capabilities.V1.SkipSupported Whether an entityset supports $skip .

Org.OData.Capabilities.V1.SearchRestrictions What kinds of restrictions on $search expressions are


applied to an entityset.

Org.OData.Capabilities.V1.CountRestrictions What kinds of restrictions on /$count path suffix and


$count=true system query option.

Org.OData.Capabilities.V1.NavigationRestrictions What kinds of restrictions on navigating properties


according to OData URL conventions.

Org.OData.Capabilities.V1.ChangeTracking The change tracking capabilities of this service or entity set.

Org.OData.Capabilities.V1.ConformanceLevel The conformance level achieved by this service.

Org.OData.Capabilities.V1.FilterFunctions A list of functions and operators supported in $filter .

Org.OData.Core.V1.DereferenceableIDs Whether Entity-ids are URLs that locate the identified entity.

Org.OData.Core.V1.ConventionalIDs Whether Entity-ids follow OData URL conventions.

Org.OData.Capabilities.V1.AsynchronousRequestsSupported Whether the service supports the asynchronous request


preference.

Org.OData.Capabilities.V1.BatchContinueOnErrorSupported Whether the service supports the continue on error


preference. This setting supports $batch requests.
T ERM DESC RIP T IO N

Org.OData.Capabilities.V1.CrossJoinSupported Whether cross joins for the entity sets in this container are
supported.

Org.OData.Capabilities.V1.SupportedFormats The media types of supported formats, including format


parameters.

Entity types
The Web API EntityType Reference lists each of the system entity types exposed through the web API which store
business data. An entity type is a named structured type with a key. It defines the named properties and
relationships of an entity. Entity types may derive by single inheritance from other entity types. Web API
Metadata EntityType Reference lists each of the entity types used to manage system metadata. Both are entity
types but the way you work with them is different. See Use the Web API with Microsoft Dataverse metadata for
information about using model entities. Each entity type is included within an EntityType element in the
$metadata . The following is the definition of the account EntityType/ from the $metadata with properties and
navigation properties removed.

<EntityType Name="account" BaseType="mscrm.crmbaseentity">


<Key>
<PropertyRef Name="accountid" />
</Key>
<!--Properties and navigation properties removed for brevity-->
<Annotation Term="Org.OData.Core.V1.Description" String="Business that represents a customer or potential
customer. The company that is billed in business transactions." />
</EntityType>

Each EntityType reference page in the Web API documentation uses information from the $metadata to show
the following information when available.

IN F O RM AT IO N DESC RIP T IO N

Description A description of the entity.

The EntityMetadata EntityType/ Description property


information is included in the EntityType element using
the Annotation element with the Term attribute value of
Org.OData.Core.V1.Description.

Collection URL The URL to access the collection of each type.

The EntityMetadata EntityType/ EntitySetName property


information is included using the $metadata
EntityContainer element. The Name attribute of each
EntitySet element controls how the data is accessed via
the URL.

Base Type This is the entity type that the entity type inherits from.

The BaseType attribute of the EntityType element


contains the name of the entity type. This name is prefixed
with the alias for the Microsoft.Dynamics.CRM namespace:
mscrm. More information:Type inheritance
IN F O RM AT IO N DESC RIP T IO N

Display Name This information is not in the $metadata , it’s retrieved from
the EntityMetadata EntityType/ DisplayName property.

Primar y Key The property value that contains the unique identifier to
refer to an instance of an entity.

The EntityMetadata EntityType/ PrimaryIdAttribute


property value is included in the EntityType Key
element. Each entity can have just one primary key.

Alternate keys are not listed here. More


information:Alternate keys

Primar y Attribute Many entities require that a primary attribute value be set,
so this is included for convenience.

This information is not in the $metadata , it’s retrieved from


the metadata EntityMetadata EntityType/
PrimaryNameAttribute property.

Proper ties See Properties.

Single-valued navigation proper ties See Single-Valued navigation properties.

Collection-valued navigation proper ties See Collection-valued navigation properties.

Operations bound to the entity type When an operation is bound to a specific entity type, it’s
listed for convenience.

Operations that use the entity type This list shows any operations that may use the entity type.
This is derived by collecting references to all operations that
refer to the current type in the parameters or as a return
value.

Entity types that inherit from the entity type This list includes any entity types that inherit directly from
the entity type. See Type inheritance for more information.

Change the name of an entity set


By default, the entity set name matches the EntityMetadata EntityType/ LogicalCollectionName (EntityMetadata
LogicalCollectionName ) property value. If you have a custom entity that you want to address using a different
entity set name, you can update the EntityMetadata EntityType/ EntitySetName (EntityMetadata. EntitySetName )
property value to use a different name.

Alternate keys
Although Dataverse allows for creating alternate keys, only the primary key will be found in the default entities.
None of the system entities have alternate keys defined. If you define alternate keys for an entity, they will be
included in the $metadata EntityType element as an Annotation like the following:
<Annotation Term="OData.Community.Keys.V1.AlternateKeys">
<Collection>
<Record Type="OData.Community.Keys.V1.AlternateKey">
<PropertyValue Property="Key">
<Collection>
<Record Type="OData.Community.Keys.V1.PropertyRef">
<PropertyValue Property="Alias" String="key name" />
<PropertyValue Property="Name" PropertyPath="key name" />
</Record>
</Collection>
</PropertyValue>
</Record>
</Collection>
</Annotation>

Information about alternate keys can also be retrieved from the metadata using the EntityMetadata EntityType/
Keys collection-valued navigation property using the Web API or the EntityMetadata. Keys property using the
organization service.

Type inheritance
Inheritance allows for sharing of common properties and categorizing entity types into groups. All entity types
in the web API inherit from two of the following entity types. All business entity types inherit from the
crmbaseentity EntityType/ and all model entities inherit from the crmmodelbaseentity EntityType/.

B A SE EN T IT Y DESC RIP T IO N

crmbaseentity EntityType/ All business entities inherit from this entity. It has no
properties. It only serves as an abstract base entity.

activitypointer EntityType/ All activity entities inherit from this entity. It defines the
common set of properties and navigation properties for
activity entities.

principal EntityType/ The systemuser EntityType/ and team EntityType/ inherit a


common ownerid property from this entity.

crmmodelbaseentity EntityType/ Only MetadataBase EntityType/ inherits directly from this


entity. It has no properties. It only serves as an abstract base
entity.

MetadataBase EntityType/) All metadata entities inherit from this entity. It provides the
MetadataId and HasChanged properties for all metadata
entities.

AttributeMetadata EntityType/ All model entities that represent different types of attributes
inherit from this entity.

EnumAttributeMetadata EntityType/ Those model entities that represent attributes that include a
set of options inherit from this entity.

OptionSetMetadataBase EntityType/ This model entity type provides a common set of properties
used by the BooleanOptionSetMetadata EntityType/ and
OptionSetMetadata EntityType/ model entity types that
inherit from it.
B A SE EN T IT Y DESC RIP T IO N

RelationshipMetadataBase EntityType/ This entity type provides a common set of properties used
by the ManyToManyRelationshipMetadata EntityType/ and
OneToManyRelationshipMetadata EntityType/ model entity
types that inherit from it.

Properties
Each entity type may have declared properties that correspond to attributes. In the Web API EntityType
Reference and Web API Metadata EntityType Reference content, properties that are inherited from a base entity
type are combined within the list of declared properties for each entity type. The inheritance is called out in the
description for each property.
In the $metadata EntityType elements each property is included in a Property element with a Name attribute
value that corresponds to the properties you will set in code. The Type attribute value specifies the data type of
the property. Properties for business entity types generally use OData primitive types.
The following is an example of the account EntityType/ name property defined in the $metadata .

<Property Name="name" Type="Edm.String" Unicode="false">


<Annotation Term="Org.OData.Core.V1.Description" String="Type the company or business name." />
</Property>

The description of the property is available in an Annotation element with the Term attribute property of
Org.OData.Core.V1.Description. This description is taken from the AttributeMetadata EntityType/ Description
property value. Not all properties have a description.
Each property may be computed. This means that the value may be set by the system. This is specified in an
Annotation element with the Term attribute value of Org.OData.Core.V1.Computed.

Each property may also have limitations on whether it may be updated. This is defined in an Annotation
element with a Term attribute value Org.OData.Core.V1.Permissions. The only option set for this is
Org.OData.Core.V1.PermissionType/Read, which indicates that the property is read only.

Primitive types
OData supports a wide range of data types but Dataverse doesn’t use all of them. The following table describes
how Dataverse Organization service types are mapped to OData primitive types.

O RGA N IZ AT IO N SERVIC E T Y P E W EB A P I T Y P E DESC RIP T IO N

BigInt Edm.Int64 Signed 64-bit integer

Boolean Edm.Boolean Binary-valued logic

CalendarRules Single-valued navigation properties Specific single-valued navigation


properties to the calendarrule
EntityType/.
O RGA N IZ AT IO N SERVIC E T Y P E W EB A P I T Y P E DESC RIP T IO N

Customer Single-valued navigation properties The customer of an entity with this


type of property may be a single-
valued navigation property set to
either an account or contact entity
type using the respective single-valued
navigation properties. When either of
the respective single-valued collection
properties is set, the other is cleared.

DateTime Edm.DateTimeOffset Date and time with a time-zone offset,


no leap seconds
There is no DateTime type in OData.

Decimal Edm.Decimal Numeric values with fixed precision


and scale

Double Edm.Double IEEE 754 binary64 floating-point


number (15-17 decimal digits)

EntityName Edm.String Sequence of UTF-8 characters

Image Edm.Binary Binary data

Integer Edm.Int32 Signed 32-bit integer

Lookup Single-valued navigation property A reference to a specific entity

ManagedProperty Not applicable For internal use only.

Memo Edm.String Sequence of UTF-8 characters

Money Edm.Decimal Numeric values with fixed precision


and scale

Owner Single-valued navigation property A reference to the principal


EntityType/. Both systemuser and team
entity types inherit their ownerid
property from the principal entity type.

Picklist Edm.Int32 Signed 32-bit integer

State Edm.Int32 Signed 32-bit integer

Status Edm.Int32 Signed 32-bit integer

String Edm.String Sequence of UTF-8 characters

Uniqueidentifier Edm.Guid 16-byte (128-bit) unique identifier

Lookup properties
For most single-valued navigation properties you will find a computed, read-only property that uses the
following naming convention: _<name>_value where the <name> matches the name of the single-valued
navigation property. The exception to this pattern is when a lookup attribute of the entity can accept multiple
types of entity references. A common example is how the incident entity customerid attribute may be set to a
reference that is either a contact or account entity. In the incident EntityType/ Single-valued navigation
properties you will find customerid_account and customerid_contact as separate single-valued navigation
properties to reflect the customer associated with an opportunity. If you set one of these single-valued
navigation properties, the other will be set to null because they are both bound to the customerid attribute. In
the incident EntityType/ Properties you’ll find a _customerid_value lookup property that contains the same
value that is set for whichever of the single-valued navigation properties contain a value.
Generally, you should avoid using lookup properties and use the corresponding single-valued navigation
properties instead. These properties have been included because they may be useful for certain integration
scenarios. These properties are read-only and computed because they will simply reflect the changes applied
using the corresponding single-valued navigation property.
When you include lookup properties in a query, you can request annotations to be included that provide
additional information about the data that is set for those underlying attributes which aren’t represented by a
single-valued navigation property. More information:Retrieve data about lookup properties

Navigation properties
In OData, navigation properties allow you to access data related to the current entity. When you retrieve an
entity you can choose to expand navigation properties to include the related data. There are two types of
navigation properties: single-valued and collection-valued.

Single -valued navigation properties


These properties correspond to Lookup attributes that support many-to-one relationships and allow setting a
reference to another entity. In the $metadata EntityType element, these are defined as a NavigationProperty
element with at Type attribute set to a single type. The following is an example of the account EntityType/
createdby single-valued navigation property in the $metadata :

<NavigationProperty Name="createdby" Type="mscrm.systemuser" Nullable="false"


Partner="lk_accountbase_createdby">
<ReferentialConstraint Property="_createdby_value" ReferencedProperty="systemuserid" />
</NavigationProperty>

Every navigation property that represents a single-valued navigation property will have a corresponding
collection-valued navigation property indicated by the Partner attribute value. Each single-valued navigation
property also has a ReferentialConstraint element with Property attribute value that represents the computed
read-only lookup property that can be used to retrieve corresponding GUID value of the related entity. More
information:Lookup properties

Collection-valued navigation properties


These properties correspond to one-to-many or many-to-many relationships. In the $metadata EntityType
element, these are defined as a NavigationProperty element with at Type attribute set to a collection of a type.
The following represents the account EntityType/ Account_Tasks collection-valued navigation property which
represents a one-to-many relationship:

<NavigationProperty Name="Account_Tasks" Type="Collection(mscrm.task)"


Partner="regardingobjectid_account_task" />

When the collection-valued navigation property represents a many-to-many relationship, the name of the
navigation property and the name of the partner will be the same. The following represents the account
EntityType/ accountleads_association collection-valued navigation property which represents a many-to-many
relationship:

<NavigationProperty Name="accountleads_association" Type="Collection(mscrm.lead)"


Partner="accountleads_association" />

The difference between one-to-many and many-to-many relationships is not important when using the Web
API. The way you associate entities is the same regardless of the type of relationship. Although many-to-many
relationships still use intersect entities behind the scenes, only a few special system intersect entities are
included within the Web API EntityType Reference. For example, campaignactivityitem EntityType/ is technically
an intersect entity, but it is included because it has more properties than an ordinary intersect entity.
An ordinary intersect entity has only the four basic properties required to maintain the many-to-many
relationship. When you create a custom many-to-many relationship between entities, an ordinary intersect
entity will be created to support the relationship. Because you should use navigation properties to perform
operations involving many-to-many relationships, ordinary intersect entities are not documented but are still
available using the Web API. These intersect entity types are accessible using an entity set name that uses the
following naming convention: <intersect entity logical name>+’collection’. For example, you can retrieve
information from the contactleads intersect entity type using
[Organization URI]/api/data/v9.0/contactleadscollection . You should only use these ordinary intersect entities
in situations where you wish to apply change tracking.

Actions
Actions are operations that allow side effects, such as data modification, and cannot be further composed in
order to avoid non-deterministic behavior.
The Web API Action Reference topic lists each of the available system actions. More information:Use Web API
actions.

Functions
Functions are operations that do not have side effects and may support further composition, for example, with
additional filter operations, functions or an action
There are two types of functions defined in the Web API:
The Web API Function Reference lists each of the available system functions.
The Web API Query Function Reference topic lists functions which are intended to be used as criteria in a
query.
More information:Use Web API functions

Complex types
Complex types are keyless named structured types consisting of a set of properties. Complex types are
commonly used as property values in model entities, or as parameters or return values for operations.
Web API ComplexType Reference lists all the system complex types. The following is the WhoAmIResponse
ComplexType" / from the $metadata.
<ComplexType Name="WhoAmIResponse">
<Property Name="BusinessUnitId" Type="Edm.Guid" Nullable="false" />
<Property Name="UserId" Type="Edm.Guid" Nullable="false" />
<Property Name="OrganizationId" Type="Edm.Guid" Nullable="false" />
</ComplexType>

Enumeration types
Enumeration types or EnumTypes are named primitive types whose values are named constants with underlying
integer values.
Web API EnumType Reference lists all the system enumeration types. Enumeration types are named primitive
types whose values are named constants with underlying integer values. The following is the AccessRights
EnumType" / from the $metadata.

<EnumType Name="AccessRights">
<Member Name="None" Value="0" />
<Member Name="ReadAccess" Value="1" />
<Member Name="WriteAccess" Value="2" />
<Member Name="AppendAccess" Value="4" />
<Member Name="AppendToAccess" Value="16" />
<Member Name="CreateAccess" Value="32" />
<Member Name="DeleteAccess" Value="65536" />
<Member Name="ShareAccess" Value="262144" />
<Member Name="AssignAccess" Value="524288" />
</EnumType>

See also
Use the Dataverse Web API
Authenticate to Dataverse with the Web API
Perform operations using the Web API
Perform operations using the Web API
4/30/2021 • 2 minutes to read • Edit Online

The Web API provides a RESTful web service interface that you can use to interact with data in Microsoft
Dataverse using a wide variety of programming languages and libraries.

NOTE
Information under this section is also applicable to the Dynamics 365 Customer Engagement (on-premises) users.

In this section
Compose Http requests and handle errors
Query data using the Web API
Retrieve and execute predefined queries
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Manage duplicate detection during Create and Update operations
See also
Use the Dataverse Web API
Authenticate to Dataverse with the Web API
Web API types, functions and actions
Compose HTTP requests and handle errors
9/26/2021 • 11 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

You interact with the Web API by composing and sending HTTP requests. You need to know how to set the
appropriate HTTP headers and handle any errors included in the response.

Web API URL and versions


To access the Web API you must compose a URL using the parts in the following table.

PA RT DESC RIP T IO N

Protocol https://

Environment Name The unique name that applies to your environment. If your
company name is Contoso, then it may be contoso .

Region Your environment will usually be available in a data center


that is close to you geographically.
North America: crm
South America: crm2
Canada: crm3
Europe, Middle East and Africa (EMEA): crm4
Asia Pacific Area (APAC): crm5
Oceania: crm6
Japan: crm7
India: crm8
North America 2: crm9
United Kingdom: crm11
France: crm12
More values will be added over time as new data center
regions are opened.

Base URL dynamics.com.

Web API path The path to the web API is /api/data/ .

Version The version is expressed this way:


v[Major_version].[Minor_version][PatchVersion]/ . The
valid version for this release is v9.1 .

Resource The name of the entity (table), function, or action you want
to use.

The URL you will use will be composed with these parts: Protocol + Environment Name + Region + Base URL +
Web API path + Version + Resource.

Version compatibility
This release introduces capabilities which are not available in previous versions. Subsequent minor versions
may provide additional capabilities which will not be back ported to earlier minor versions. Your code written for
v9.0 will continue to work in future versions when you reference v9.0 in the URL you use.
As new capabilities are introduced they may conflict with earlier versions. This is necessary to allow the service
to become better. Most of the time, capabilities will remain the same between versions but you should not
assume they will.

NOTE
Unlike the v8.x minor releases, new capabilities or other changes added to future versions will not be applied to earlier
versions. You will need to pay attention to the version of the service you use and test your code if you change the version
used.

HTTP methods
HTTP requests can apply a variety of different methods. When using the web API you will only use the methods
listed in the following table.

M ET H O D USA GE

GET Use when retrieving data, including calling functions. The


expected Status Code for a successful retrieve is 200 OK.

POST Use when creating entities or calling actions.

PATCH Use when updating entities or performing upsert operations.

DELETE Use when deleting entities or individual properties of


entities.

PUT Use in limited situations to update individual properties of


entities. This method isn't recommended when updating
most entities. You'll use this when updating table definitions.

HTTP headers
Although the OData protocol allows for both JSON and ATOM format, the web API only supports JSON.
Therefore the following headers can be applied.
Every request should include the Accept header value of application/json , even when no response body is
expected. Any error returned in the response will be returned as JSON. While your code should work even if this
header isn't included, we recommend including it as a best practice
The current OData version is 4.0, but future versions may allow for new capabilities. To ensure that there is no
ambiguity about the OData version that will be applied to your code at that point in the future, you should
always include an explicit statement of the current OData version and the Maximum version to apply in your
code. Use both OData-Version and OData-MaxVersion headers set to a value of 4.0 .
Queries which expand collection-valued navigation properties may return cached data for those properties that
doesn't reflect recent changes. Include If-None-Match: null header in the request body to override browser
caching of Web API request. For more information see Hypertext Transfer Protocol (HTTP/1.1): Conditional
Requests 3.2 : If-None-Match.
All HTTP requests should include at least the following headers.

Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
If-None-Match: null

Every request that includes JSON data in the request body must include a Content-Type header with a value of
application/json .
Content-Type: application/json

You can use additional headers to enable specific capabilities.


To return data on create (POST) or update (PATCH) operations for entities, include the
return=representation preference. When this preference is applied to a POST request, a successful
response will have status 201 (Created) . For a PATCH request, a successful response will have a status
200 (OK). Without this preference applied, both operations will return status 204 (No Content) to reflect
that no data is returned in the body of the response by default.
To return formatted values with a query, include the odata.include-annotations preference set to
Microsoft.Dynamics.CRM.formattedvalue using the Prefer header. More information:Include formatted
values
You also use the Prefer header with the odata.maxpagesize option to specify how many pages you want
to return. More information:Specify the number of tables (entities) to return in a page
To impersonate another user when the caller has the privileges to do so, add the CallerObjectId header
with the user's Azure Active Directory Object Id value of the user to impersonate. This data is in the
SystemUser table/entity AzureActiveDirectoryObjectId attribute (column). More information:Impersonate
another user using the Web API.
To apply optimistic concurrency, you can apply the If-Match header with an Etag value. More
information:Apply optimistic concurrency.
To control whether an upsert operation should actually create or update an entity, you can also use the
If-Match and If-None-Match headers. More information:Upsert a table (entity).

When you execute batch operations, you must apply a number of different headers in the request and
with each part sent in the body. More information: Execute batch operations using the Web API.
When you create a solution component and want to associate it with a solution, use the
MSCRM.SolutionUniqueName request header and set the value to the unique name of the solution.

When you want to enable duplicate detection when creating a new entity record, set the
MSCRM.SuppressDuplicateDetection request header value to false. More information: Check for Duplicate
records
When you want to by-pass custom plug-in code and the caller has the prvBypassCustomPlugins privilege,
set the MSCRM.BypassCustomPluginExecution request header to true . More information: Bypass Custom
Business Logic

Identify status codes


Whether an http request succeeds or fails, the response will include a status code. Status codes returned by the
Microsoft Dataverse Web API include the following.

C O DE DESC RIP T IO N TYPE

200 OK Expect this when your operation will Success


return data in the response body.

201 Created Expect this when your entity POST Success


operation succeeds and you have
specified the return=representation
preference in your request.

204 No Content Expect this when your operation Success


succeeds but does not return data in
the response body.
C O DE DESC RIP T IO N TYPE

304 Not Modified Expect this when testing whether an Redirection


entity has been modified since it was
last retrieved. More information:
Conditional retrievals

403 Forbidden Expect this for the following types of Client Error
errors:

- AccessDenied
- AttributePermissionReadIsMissing
-
AttributePermissionUpdateIsMissingD
uringUpdate
- AttributePrivilegeCreateIsMissing
- CannotActOnBehalfOfAnotherUser
-
CannotAddOrActonBehalfAnotherUser
Privilege
- CrmSecurityError
- InvalidAccessRights
- PrincipalPrivilegeDenied
-
PrivilegeCreateIsDisabledForOrganizati
on
- PrivilegeDenied
- unManagedinvalidprincipal
- unManagedinvalidprivilegeedepth

401 Unauthorized Expect this for the following types of Client Error
errors:

- BadAuthTicket
- ExpiredAuthTicket
- InsufficientAuthTicket
- InvalidAuthTicket
- InvalidUserAuth
- MissingCrmAuthenticationToken
-
MissingCrmAuthenticationTokenOrgan
izationName
- RequestIsNotAuthenticated
- TamperedAuthTicket
- UnauthorizedAccess
- UnManagedInvalidSecurityPrincipal

413 Payload Too Large Expect this when the request length is Client Error
too large.

400 BadRequest Expect this when an argument is Client Error


invalid.

404 Not Found Expect this when the resource doesn't Client Error
exist.

405 Method Not Allowed This error occurs for incorrect method Client Error
and resource combinations. For
example, you can't use DELETE or
PATCH on a collection of entities.

Expect this for the following types of


errors:

- CannotDeleteDueToAssociation
- InvalidOperation
- NotSupported
C O DE DESC RIP T IO N TYPE

412 Precondition Failed Expect this for the following types of Client Error
errors:

- ConcurrencyVersionMismatch
- DuplicateRecord

429 Too Many Requests Expect this when API limits are Client Error
exceeded. More information: Service
Protection API Limits

501 Not Implemented Expect this when some requested Server Error
operation isn't implemented.

503 Service Unavailable Expect this when the web API service Server Error
isn't available.

Parse errors from the response


Details about errors are included as JSON in the response. Errors will be in this format.

{
"error":{
"code": "<This code is not related to the http status code and is frequently empty>",
"message": "<A message describing the error>"
}
}

IMPORTANT
The structure of the error messages is changing. This change is expected to be deployed to different regions over a period
starting in August through October 2020.
Before this change, the errors returned were in this format:

{
"error":{
"code": "<This code is not related to the http status code and is frequently empty>",
"message": "<A message describing the error>",
"innererror": {
"message": "<A message describing the error, this is frequently the same as the outer message>",
"type": "Microsoft.Crm.CrmHttpException",
"stacktrace": "<Details from the server about where the error occurred>"
}
}
}

We are removing the innererror property of the error message. You should remove any code that expects to parse this
property.
The OData Error Response guidance states "The innererror name/value pair SHOULD only be used in development
environments in order to guard against potential security concerns around information disclosure.". To align with this
guidance we are removing this property.
If you find that an application you use has a dependency on this property after this change is deployed, you can contact
support and request that the change be temporarily removed for your environment. This will provide time for the
application developer to make appropriate changes to remove this dependency.

Include additional details with errors


Some errors can include additional details using annotations. When a request includes the
Prefer: odata.include-annotations="*" header, the response will include all the annotations which will include
additional details about errors and a URL that can be used to be directed to any specific guidance for the error.
Some of these details can be set by developers writing plug-ins. For example, let’s say you have a plug-in that
throws an error using the InvalidPluginExecutionException(OperationStatus, Int32, String) constructor. This
allows you to pass an OperationStatus value, a custom integer error code, and an error message.
A simple plug-in might look like this:

namespace MyNamespace
{
public class MyClass : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{

// Obtain the tracing service


ITracingService tracingService =
(ITracingService)serviceProvider.GetService(typeof(ITracingService));

tracingService.Trace("Entering MyClass plug-in.");

try
{
throw new InvalidPluginExecutionException(OperationStatus.Canceled, 12345, "Example Error
Message.");
}
catch (InvalidPluginExecutionException ex)
{
tracingService.Trace("StackTrace:");
tracingService.Trace(ex.StackTrace);
throw ex;
}
}
}
}

When this plug-in is registered on the create of an account entity, and the request to create an account includes
the odata.include-annotations="*" preference, the request and response will look like the following:
Request

POST https://yourorg.api.crm.dynamics.com/api/data/v9.1/accounts HTTP/1.1


Content-Type: application/json;
Prefer: odata.include-annotations="*"
{
"name":"Example Account"
}

Response

HTTP/1.1 400 Bad Request


Content-Type: application/json; odata.metadata=minimal
{
"error": {
"code": "0x80040265",
"message": "Example Error Message.",
"@Microsoft.PowerApps.CDS.ErrorDetails.OperationStatus": "1",
"@Microsoft.PowerApps.CDS.ErrorDetails.SubErrorCode": "12345",
"@Microsoft.PowerApps.CDS.HelpLink": "http://go.microsoft.com/fwlink/?
LinkID=398563&error=Microsoft.Crm.CrmException%3a80040265&client=platform",
"@Microsoft.PowerApps.CDS.TraceText": "\r\n[MyNamespace: MyNamespace.MyClass ]\r\n[52e2dbb9-85d3-
ea11-a812-000d3a122b89: MyNamespace.MyClass : Create of account] \r\n\r\n Entering MyClass plug-
in.\r\nStackTrace:\r\n at MyNamespace.MyClass.Execute(IServiceProvider serviceProvider)\r\n\r\n"
"@Microsoft.PowerApps.CDS.InnerError.Message": "Example Error Message."
}
}

This response includes the following annotations:


A N N OTAT IO N A N D DESC RIP T IO N VA L UE

@Microsoft.PowerApps.CDS.ErrorDetails.OperationStatus 1
The value of the OperationStatus set by the
InvalidPluginExecutionException(OperationStatus, Int32,
String) constructor.

@Microsoft.PowerApps.CDS.ErrorDetails.SubErrorCode 12345
The value of the SubErrorCode set by the
InvalidPluginExecutionException(OperationStatus, Int32,
String) constructor.

@Microsoft.PowerApps.CDS.HelpLink http://go.microsoft.com/fwlink/?
A URL that contains information about the error which may LinkID=398563&error=Microsoft.Crm.CrmException%3a80040265&client=platform
re-direct you to guidance about how to address the error.

@Microsoft.PowerApps.CDS.TraceText [MyNamespace: MyNamespace.MyClass ]


Content written to the Plug-in trace log using the [52e2dbb9-85d3-ea11-a812-000d3a122b89:
ITracingService.Trace(String, Object[]) Method. This includes MyNamespace.MyClass :Create of account]
the stacktrace for the plugin because the plug-in author
logged it. Entering MyClass plug-in.
StackTrace:
at MyNamespace.MyClass.Execute(IServiceProvider
serviceProvider)

@Microsoft.PowerApps.CDS.InnerError.Message Example Error Message.


The error message found in the InnerError for the exception.
This should be the same as the error message except in
certain special cases that are for internal use only.

NOTE
The @Microsoft.PowerApps.CDS.HelpLink is not guaranteed to provide guidance for every error. Guidance may be
provided proactively but most commonly it will be provided reactively based on how frequently the link is used. Please
use the link. If it doesn't provide guidance, your use of the link helps us track that people need more guidance about the
error. We can then prioritize including guidance to the errors that people need most. The resources that the link may
direct you to may be documentation, links to community resources, or external sites.

If you do not want to receive all annotations in the response, you can specify which specific annotations you
want to have returned. Rather than using Prefer: odata.include-annotations="*" , you can use the following to
receive only formatted values for operations that retrieve data and the helplink if an error occurs:
Prefer: odata.include-
annotations="OData.Community.Display.V1.FormattedValue,Microsoft.PowerApps.CDS.HelpLink"
.

Add a Shared Variable from the Web API


You can set a string value that will be available to plug-ins within the ExecutionContext in the SharedVariables
collection. More information: Shared variables
To pass this value using the Web API, simply use the tag query option.
For example: ?tag=This is a value passed.

Will result in the following value within the SharedVariables collection when sent using a webhook.

{
"key": "tag",
"value": "This is a value passed."
}

This can also be done using the Organization Service: Add a Shared Variable from the Organization Service
See also
Perform operations using the Web API
Query data using the Web API
Create a table (entity) using the Web API
Retrieve a table (entity) using the Web API
Update and delete tables (entities) using the Web API
Associate and disassociate tables (entities) using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Query data using the Web API
10/7/2021 • 16 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

If you want to retrieve data for an entity set, use a GET request. When retrieving data, you can apply query
options to set criteria for the entity (table) data you want and the entity properties (columns) that should be
returned.

Basic query example


This example queries the accounts entity set and uses the $select and $top system query options to return
the name property for the first three accounts:
Request

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$top=3 HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#accounts(name)",
"value":[
{
"@odata.etag":"W/\"501097\"",
"name":"Fourth Coffee (sample)",
"accountid":"89390c24-9c72-e511-80d4-00155d2a68d1"
},
{
"@odata.etag":"W/\"501098\"",
"name":"Litware, Inc. (sample)",
"accountid":"8b390c24-9c72-e511-80d4-00155d2a68d1"
},
{
"@odata.etag":"W/\"501099\"",
"name":"Adventure Works (sample)",
"accountid":"8d390c24-9c72-e511-80d4-00155d2a68d1"
}
]
}
Limits on number of table rows (entities) returned
Unless you specify a smaller page size, a maximum of 5000 rows will be returned for each request. If there are
more rows that match the query filter criteria, a @odata.nextLink property will be returned with the results. Use
the value of the @odata.nextLink property with a new GET request to return the next page of rows.

NOTE
Queries on entity (table) definitions aren’t limited or paged. More information:Query table definitions using the Web API

Use $top query option


You can limit the number of results returned by using the $top system query option. The following example
will return just the first three account rows.

GET [Organization URI]/api/data/v9.1/accounts?$select=name,revenue&$top=3

NOTE
Limiting results using $top will prevent odata.maxpagesize preference from being applied. You can use
odata.maxpagesize preference or $top , but not both at the same time. For more information about
odata.maxpagesize , see Specify the number of rows to return in a page.
You should also not use $top with $count .

Specify the number of rows to return in a page


Use the odata.maxpagesize preference value to request the number of rows returned in the response.

NOTE
You can’t use an odata.maxpagesize preference value greater than 5000.

The following example queries the accounts entity set and returns the name property for the first three
accounts.
Request

GET [Organization URI]/api/data/v9.1/accounts?$select=name HTTP/1.1


Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: odata.maxpagesize=3

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 402
Preference-Applied: odata.maxpagesize=3

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#accounts(name)",
"value":[
{
"@odata.etag":"W/\"437194\"",
"name":"Fourth Coffee (sample)",
"accountid":"7d51925c-cde2-e411-80db-00155d2a68cb"
},
{
"@odata.etag":"W/\"437195\"",
"name":"Litware, Inc. (sample)",
"accountid":"7f51925c-cde2-e411-80db-00155d2a68cb"
},
{
"@odata.etag":"W/\"468026\"",
"name":"Adventure Works (sample)",
"accountid":"8151925c-cde2-e411-80db-00155d2a68cb"
}
],
"@odata.nextLink":"[Organization URI]/api/data/v9.1/accounts?
$select=name&$skiptoken=%3Ccookie%20pagenumber=%222%22%20pagingcookie=%22%253ccookie%2520page%253d%25221%252
2%253e%253caccountid%2520last%253d%2522%257b8151925C-CDE2-E411-80DB-
00155D2A68CB%257d%2522%2520first%253d%2522%257b7D51925C-CDE2-E411-80DB-
00155D2A68CB%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20/%3E"
}

Use the value of the @odata.nextLink property to request the next set of records. Don’t change or append any
additional system query options to the value. For every subsequent request for additional pages, you should use
the same odata.maxpagesize preference value used in the original request. Also, cache the results returned or
the value of the @odata.nextLink property so that previously retrieved pages can be returned to.

NOTE
The value of the @odata.nextLink property is URI encoded. If you URI encode the value before you send it, the XML
cookie information in the URL will cause an error.

Apply system query options


Each of the system query options you append to the URL for the entity set is added using the syntax for query
strings. The first is appended after [?] and subsequent query options are separated using [&]. All query options
are case-sensitive as shown in the following example.

GET [Organization URI]/api/data/v9.1/accounts?$select=name,revenue


&$top=3
&$filter=revenue gt 100000

Request specific properties


Use the $select system query option to limit the properties returned as shown in the following example.
GET [Organization URI]/api/data/v9.1/accounts?$select=name,revenue

IMPORTANT
This is a performance best practice. If properties aren’t specified using $select , all properties will be returned.

When you request certain types of properties you can expect additional read-only properties to be returned
automatically.
If you request a money value, the _transactioncurrencyid_value lookup property will be returned. This property
contains only the GUID value of the transaction currency so you could use this value to retrieve information
about the currency using the transactioncurrency EntityType /. Alternatively, by requesting annotations you can
also get additional data in the same request. More information: Retrieve data about lookup properties
If you request a property that is part of a composite attribute for an address, you will get the composite
property as well. For example, if your query requests the address1_line1 property for a contact, the
address1_composite property will be returned as well.

Filter results
Use the $filter system query option to set criteria for which rows will be returned.

Standard filter operators


The Web API supports the standard OData filter operators listed in the following table.

O P ERATO R DESC RIP T IO N EXA M P L E

Comparison Operators

eq Equal $filter=revenue eq 100000

ne Not Equal $filter=revenue ne 100000

gt Greater than $filter=revenue gt 100000

ge Greater than or equal $filter=revenue ge 100000

lt Less than $filter=revenue lt 100000

le Less than or equal $filter=revenue le 100000

Logical Operators

and Logical and $filter=revenue lt 100000 and


revenue gt 2000

or Logical or $filter=contains(name,'(sample)')
or contains(name,'test')

not Logical negation $filter=not


contains(name,'sample')
O P ERATO R DESC RIP T IO N EXA M P L E

Grouping Operators

( ) Precedence grouping (contains(name,'sample') or


contains(name,'test')) and
revenue gt 5000

NOTE
This is a sub-set of the 11.2.5.1.1 Built-in Filter Operations. Arithmetic operators and the comparison has operator are not
supported in the Web API.
All filter conditions for string values are case insensitive.

Standard query functions


The Web API supports these standard OData string query functions:

F UN C T IO N EXA M P L E

contains $filter=contains(name,'(sample)')

endswith $filter=endswith(name,'Inc.')

startswith $filter=startswith(name,'a')

NOTE
This is a sub-set of the 11.2.5.1.2 Built-in Query Functions. Date , Math , Type , Geo and other string functions aren’t
supported in the web API.

Microsoft Dataverse Web API query functions


Dataverse provides a number of special functions that accept parameters, return Boolean values, and can be
used as filter criteria in a query. See Web API Query Function Reference for a list of these functions. The
following is an example of the Between Function / searching for accounts with a number of employees between
5 and 2000.

GET [Organization URI]/api/data/v9.1/accounts?$select=name,numberofemployees


&$filter=Microsoft.Dynamics.CRM.Between(PropertyName='numberofemployees',PropertyValues=["5","2000"])

More information: Compose a query with functions.

Use Lambda operators


The Web API allows you to use two lambda operators, which are any and all to evaluate a Boolean
expression on a collection.

any operator
The any operator returns true if the Boolean expression applied is true for any member of the collection,
otherwise it returns false . The any operator without an argument returns true if the collection is not empty.
Example
The example given below shows how you can retrieve all account entity records that have at least one email
with "sometext" in the subject.

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$filter=Account_Emails/any(o:contains(o/subject,'sometext')) HTTP/1.1
Prefer: odata.include-annotations="*"
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

all operator
The all operator returns true if the Boolean expression applied is true for all members of the collection,
otherwise it returns false .
Example
The example given below shows how you can retrieve all account entity records that have all associated tasks
closed.

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$filter=Account_Tasks/all(o:o/statecode eq 1) HTTP/1.1
Prefer: odata.include-annotations="*"
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

The example given below shows how you can retrieve all account entity records that have at least one email
with "sometext" in the subject and whose statecode is active.

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$filter=Account_Emails/any(o:contains(o/subject,'sometext') and
o/statecode eq 0) HTTP/1.1
Prefer: odata.include-annotations="*"
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

The example given below shows how you can also create a nested query using any and all operators.

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$filter=(contact_customer_accounts/any(c:c/jobtitle eq 'jobtitle' and
c/opportunity_customer_contacts/any(o:o/description ne 'N/A'))) and
endswith(name,'{0}') HTTP/1.1
Prefer: odata.include-annotations="*"
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Filter parent rows (records) based on values of child records


The example given below shows how you can use the /any operator to retrieve all the account records that have:
any of their linked opportunity records' budget greater than or equal to 300, and
the opportunity records' have no description, or
the opportunity records' description contains the term "bad".
Request
GET [Organization URI]/api/data/v9.1/accounts?$select=name
&$filter=not opportunity_customer_accounts/any(o:o/description eq null and
o/budgetamount le 300 or
contains(o/description, 'bad')) and
opportunity_customer_accounts/any() and
endswith(name,'{0}') HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Filter rows (records) based on single -valued navigation property


Navigation properties let you access data related to the current entity. Single-valued navigation properties
correspond to Lookup attributes that support many-to-one relationships and allow setting a reference to
another entity. More information: Navigation properties.
You can filter your entity set records based on single-valued navigation property values. For example, you can
retrieve child accounts for the specified account.
For example:
Retrieve all the matching accounts for a specified Contact ID
Request

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$filter=primarycontactid/contactid eq a0dbf27c-8efb-e511-80d2-00155db07c77 HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#accounts(name)",
"value":[
{
"@odata.etag":"W/\"513479\"",
"name":"Adventure Works (sample)",
"accountid":"3adbf27c-8efb-e511-80d2-00155db07c77"
},
{
"@odata.etag":"W/\"514057\"",
"name":"Blue Yonder Airlines (sample)",
"accountid":"3edbf27c-8efb-e511-80d2-00155db07c77"
}
]
}

Retrieve child accounts for the specified Account ID


Request
GET [Organization URI]/api/data/v9.1/accounts?$select=name
&$filter=parentaccountid/accountid eq 3adbf27c-8efb-e511-80d2-00155db07c77
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#accounts(name)",
"value":[
{
"@odata.etag":"W/\"514058\"",
"name":"Sample Child Account 1",
"accountid":"915e89f5-29fc-e511-80d2-00155db07c77"
},
{
"@odata.etag":"W/\"514061\"",
"name":"Sample Child Account 2",
"accountid":"03312500-2afc-e511-80d2-00155db07c77"
}
]
}

Filter results based on values of collection-valued navigation properties

NOTE
It is possible to use $filter within $expand to filter results for related records in a Retrieve operation. You can use a
semi-colon separated list of system query options enclosed in parentheses after the name of the collection-valued
navigation property. The query options that are supported within $expand are $select , $filter , $top and
$orderby . More information: Options to apply to expanded tables.

The two options for filtering results based on values of collection-valued navigation properties are:
1. Construct a quer y using Lambda operators
Lambda operators allow you to apply filter on values of collection properties for a link-entity. The below
example retrieves the records of systemuser entity type that are linked with team and teammembership entity
types, that means it retrieves systemuser records who are also administrators of a team whose name is
"CITTEST".

GET [Organization URI]/api/data/v9.1/systemusers?$filter=(teammembership_association/any(t:t/name eq


'CITTEST'))
&$select=fullname,businessunitid,title,address1_telephone1,systemuserid
&$orderby=fullname
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

More information: Use Lambda operators.


2. Iterate over results filtering individual entities based on values in the collection using multiple
operations
To get the same results as the example above, you can retrieve records of two entity types and then iteratively
match the values in the collection of one entity to the value in the other entity, thereby filtering entities based on
the values in the collection.
Follow the steps in the below example to understand how we can filter results using the iteration method:
1. Get a distinct list of team._administratorid_value values.
GET [OrganizationURI]/api/data/v9.1/teams?
$select=_administratorid_value&$filter=_administrator_value ne null
Then loop through the returned values to remove duplicates and get a distinct list. i.e. Create a new
array, loop through the query results, for each check to see if they are already in the new array, if not,
add them. This should give you a list of distinct systemuserid values
The way you would do this in JavaScript vs C# would be different, but essentially you should be able
to get the same results.
2. Query systemuser using ContainValues Query Function / to compare the systemuserid values with the list
collected in Step 1.

Order results
Specify the order in which items are returned using the $orderby system query option. Use the asc or desc
suffix to specify ascending or descending order respectively. The default is ascending if the suffix isn’t applied.
The following example shows retrieving the name and revenue properties of accounts ordered by ascending
revenue and by descending name.

GET [Organization URI]/api/data/v9.1/accounts?$select=name,revenue


&$orderby=revenue asc,name desc
&$filter=revenue ne null

Aggregate and grouping results


By using $apply you can aggregate and group your data dynamically. Possible use cases with $apply :

USE C A SE EXA M P L E

List of unique statuses in the query accounts?$apply=groupby((statuscode))

Aggregate sum of the estimated value opportunities?$apply=aggregate(estimatedvalue with


sum as total)

Average size of the deal based on estimated value and opportunities?


status $apply=groupby((statuscode),aggregate(estimatedvalue
with average as averagevalue)

Sum of estimated value based on status opportunities?


$apply=groupby((statuscode),aggregate(estimatedvalue
with sum as total))

Total opportunity revenue by account name opportunities?


$apply=groupby((parentaccountid/name),aggregate(estimatedvalue
with sum as total))

Primary contact names for accounts in 'WA' accounts?$apply=filter(address1_stateorprovince eq


'WA')/groupby((primarycontactid/fullname))
USE C A SE EXA M P L E

Last created record date and time accounts?$apply=aggregate(createdon with max as


lastCreate)

First created record date and time accounts?$apply=aggregate(createdon with min as


firstCreate)

The aggregate functions are limited to a collection of 50,000 records. Further information around using
aggregate functionality with Dataverse can be found here: Use FetchXML to construct a query.
Additional details on OData data aggregation can be found here: OData extension for data aggregation version
4.0. Note that Dataverse supports only a sub-set of these aggregate methods.

Use parameter aliases with system query options


You can use parameter aliases for $filter and $orderby system query options. Parameter aliases allow for the
same value to be used multiple times in a request. If the alias isn’t assigned a value it is assumed to be null.
Without parameter aliases:

GET [Organization URI]/api/data/v9.1/accounts?$select=name,revenue


&$orderby=revenue asc,name desc
&$filter=revenue ne null

With parameter aliases:

GET [Organization URI]/api/data/v9.1/accounts?$select=name,revenue


&$orderby=@p1 asc,@p2 desc
&$filter=@p1 ne @p3&@p1=revenue&@p2=name

You can also use parameter aliases when using functions. More information: Use Web API functions

Retrieve a count of rows


Use the $count system query option with a value of true to include a count of entities that match the filter
criteria up to 5000.

NOTE
The count value does not represent the total number of rows in the system. It is limited by the maximum number of rows
that can be returned. More information: Limits on number of rows returned
If you want to retrieve the total number of rows for a table beyond 5000, use the RetrieveTotalRecordCount Function /.

The response @odata.count property will contain the number of rows that match the filter criteria irrespective of
an odata.maxpagesize preference limitation.

NOTE
You should not use $top with $count .

The following example shows that there are ten accounts that match the criteria where the name contains
“sample”, but only the first three accounts are returned.
Request

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$filter=contains(name,'sample')
&$count=true HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: odata.maxpagesize=3

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.maxpagesize=3

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#accounts(name)",
"@odata.count":10,
"value":[
{
"@odata.etag":"W/\"502482\"",
"name":"Fourth Coffee (sample)",
"accountid":"655eaf89-f083-e511-80d3-00155d2a68d3"
},
{
"@odata.etag":"W/\"502483\"",
"name":"Litware, Inc. (sample)",
"accountid":"675eaf89-f083-e511-80d3-00155d2a68d3"
},
{
"@odata.etag":"W/\"502484\"",
"name":"Adventure Works (sample)",
"accountid":"695eaf89-f083-e511-80d3-00155d2a68d3"
}
],
"@odata.nextLink":"[Organization URI]/api/data/v9.1/accounts?
$select=name&$filter=contains(name,'sample')&$skiptoken=%3Ccookie%20pagenumber=%222%22%20pagingcookie=%22%25
3ccookie%2520page%253d%25221%2522%253e%253caccountid%2520last%253d%2522%257b695EAF89-F083-E511-80D3-
00155D2A68D3%257d%2522%2520first%253d%2522%257b655EAF89-F083-E511-80D3-
00155D2A68D3%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E"
}

If you don’t want to return any data except for the count, you can apply $count to any collection to get just the
value.
Request

GET [Organization URI]/api/data/v9.1/accounts/$count HTTP/1.1


Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: text/plain
OData-Version: 4.0

10

Include formatted values


When you want to receive formatted values for properties with the results, use the odata.include-annotations
preference with the value of OData.Community.Display.V1.FormattedValue . The response will include these values
with properties that match the following naming convention:

<propertyname>@OData.Community.Display.V1.FormattedValue

The following example queries the accounts entity set and returns the first record, including properties that
support formatted values.
Request

GET [Organization URI]/api/data/v9.1/accounts?


$select=name,donotpostalmail,accountratingcode,numberofemployees,revenue
&$top=1 HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue"

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"

{
"@odata.context":"[Organization
URI]/api/data/v9.1/$metadata#accounts(name,donotpostalmail,accountratingcode,numberofemployees,revenue)",
"value":[
{
"@odata.etag":"W/\"502170\"",
"name":"Fourth Coffee (sample)",
"[email protected]":"Allow",
"donotpostalmail":false,
"[email protected]":"Default Value",
"accountratingcode":1,
"[email protected]":"9,500",
"numberofemployees":9500,
"[email protected]":"$100,000.00",
"revenue":100000,
"accountid":"89390c24-9c72-e511-80d4-00155d2a68d1",
"transactioncurrencyid_value":"50b6dd7b-f16d-e511-80d0-00155db07cb1"
}
]
}

Retrieve related tables with query


Use the $expand system query option in the navigation properties to control what data from related entities is
returned. More information: Retrieve related tables with query.

Retrieve data about lookup properties


If your query includes lookup properties you can request annotations that will provide additional information
about the data in these properties. Most of the time, the same data is can be derived with knowledge of the
single-valued navigation properties and the data included in the related entities. However, in cases where the
property represents a lookup attribute that can refer to more than one type of entity, this information can tell
you what type of entity is referenced by the lookup property. More information: Lookup properties
There are two additional types of annotations available for these properties,

A N N OTAT IO N DESC RIP T IO N

Microsoft.Dynamics.CRM.associatednavigationproperty The name of the single-valued navigation property that


includes the reference to the entity.

Microsoft.Dynamics.CRM.lookuplogicalname The logical name of the entity referenced by the lookup.

These properties also can include formatted values as described in Include formatted values. Just like formatted
values, you can return the other annotations using the odata.include-annotations preference set to the specific
type of annotation you want, or you can set the value to "*" and return all three. The following sample shows
the request and response to retrieve information about the incident entity _customerid_value lookup property
with annotations included.
Request

GET [Organization URI]/api/data/v9.1/incidents(39dd0b31-ed8b-e511-80d2-00155d2a68d4)?


$select=title,_customerid_value
&$expand=customerid_contact($select=fullname) HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: odata.include-annotations="*"

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="*"

{
"@odata.context":"[Organization
URI]/api/data/v9.1/$metadata#incidents(title,_customerid_value,customerid_contact(fullname))/$entity",
"@odata.etag":"W/\"504696\"",
"_customerid_value@Microsoft.Dynamics.CRM.associatednavigationproperty":"customerid_contact",
"[email protected]":"contact",
"[email protected]":"Susanna Stubberod (sample)",
"_customerid_value":"7ddd0b31-ed8b-e511-80d2-00155d2a68d4",
"incidentid":"39dd0b31-ed8b-e511-80d2-00155d2a68d4",
"customerid_contact":{
"@odata.etag":"W/\"503587\"",
"fullname":"Susanna Stubberod (sample)",
"contactid":"7ddd0b31-ed8b-e511-80d2-00155d2a68d4"
}
}

Retrieve related tables by expanding navigation properties


Retrieve related tables by expanding collection-valued navigation properties
If you expand on collection-valued navigation parameters to retrieve related entities for entity sets, an
@odata.nextLink property will be returned for the related entities. You should use the value of the
@odata.nextLink property with a new GET request to return the required data.

The following example retrieves the tasks assigned to the top 5 account records.
Request

GET [Organization URI]/api/data/v9.1/accounts?$top=5


&$select=name
&$expand=Account_Tasks($select=subject,scheduledstart) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.1/$metadata#accounts(name,Account_Tasks,Account_Tasks(subject,scheduledstart))",
"value":[
{
"@odata.etag":"W/\"513475\"",
"name":"Fourth Coffee (sample)",
"accountid":"36dbf27c-8efb-e511-80d2-00155db07c77",
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(36dbf27c-8efb-e511-80d2-
00155db07c77)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag":"W/\"513477\"",
"name":"Litware, Inc. (sample)",
"accountid":"38dbf27c-8efb-e511-80d2-00155db07c77",
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(38dbf27c-8efb-e511-80d2-
00155db07c77)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag":"W/\"514074\"",
"name":"Adventure Works (sample)",
"accountid":"3adbf27c-8efb-e511-80d2-00155db07c77",
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(3adbf27c-8efb-e511-80d2-
00155db07c77)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag":"W/\"513481\"",
"name":"Fabrikam, Inc. (sample)",
"accountid":"3cdbf27c-8efb-e511-80d2-00155db07c77",
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(3cdbf27c-8efb-e511-80d2-
00155db07c77)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag":"W/\"514057\"",
"name":"Blue Yonder Airlines (sample)",
"accountid":"3edbf27c-8efb-e511-80d2-00155db07c77",
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(3edbf27c-8efb-e511-80d2-
00155db07c77)/Account_Tasks?$select=subject,scheduledstart"
}
]
}

Retrieve related rows (records) by expanding both single -valued and collection-valued navigation properties
The following example demonstrates how you can expand related rows (records) for entity sets using both
single and collection-valued navigation properties. As explained earlier, expanding on collection-valued
navigation properties to retrieve related entities for entity sets returns an @odata.nextLink property for the
related entities. You should use the value of the @odata.nextLink property with a new GET request to return the
required data.
In this example, we are retrieving the contact and tasks assigned to the top 3 accounts.
Request

GET [Organization URI]/api/data/v9.1/accounts?$top=3


&$select=name
&$expand=primarycontactid($select=contactid,fullname),Account_Tasks($select=subject,scheduledstart)
HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.1/$metadata#accounts(name,primarycontactid,Account_Tasks,primarycontactid(contactid,fullnam
e),Account_Tasks(subject,scheduledstart))",
"value":[
{
"@odata.etag":"W/\"550614\"",
"name":"Fourth Coffee (sample)",
"accountid":"5b9648c3-68f7-e511-80d3-00155db53318",
"primarycontactid":{
"contactid":"c19648c3-68f7-e511-80d3-00155db53318",
"fullname":"Yvonne McKay (sample)"
},
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(5b9648c3-68f7-e511-80d3-
00155db53318)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag":"W/\"550615\"",
"name":"Litware, Inc. (sample)",
"accountid":"5d9648c3-68f7-e511-80d3-00155db53318",
"primarycontactid":{
"contactid":"c39648c3-68f7-e511-80d3-00155db53318",
"fullname":"Susanna Stubberod (sample)"
},
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(5d9648c3-68f7-e511-80d3-
00155db53318)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag":"W/\"550616\"",
"name":"Adventure Works (sample)",
"accountid":"5f9648c3-68f7-e511-80d3-00155db53318",
"primarycontactid":{
"contactid":"c59648c3-68f7-e511-80d3-00155db53318",
"fullname":"Nancy Anderson (sample)"
},
"Account_Tasks":[

],
"[email protected]":"[Organization URI]/api/data/v9.1/accounts(5f9648c3-68f7-e511-80d3-
00155db53318)/Account_Tasks?$select=subject,scheduledstart"
}
]
}

Use change tracking to synchronize data with external systems


The change tracking feature allows you to keep the data synchronized in an efficient manner by detecting what
data has changed since the data was initially extracted or last synchronized. Changes made in entities can be
tracked using Web API requests by adding odata.track-changes as a preference header. Preference header
odata.track-changes requests that a delta link be returned which can subsequently be used to retrieve entity
changes.
More information: Use change tracking to synchronize data with external systems.
Column comparison using the Web API
The following example shows how to compare columns using the Web API:

https://<environment-root>/contacts?$select=firstname&$filter=firstname eq lastname

More information: Use column comparison in queries


See also
Search across table data using Dataverse search
Work with Quick Find’s search item limit
Web API Query Data Sample (C#)
Web API Query Data Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Retrieve related table records with a query
10/7/2021 • 5 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Use the $expand system query option in the navigation properties to control what data from related entities is
returned. There are two types of navigation properties:
Single-valued navigation properties correspond to Lookup attributes that support many-to-one
relationships and allow setting a reference to another entity.
Collection-valued navigation properties correspond to one-to-many or many-to-many relationships.
If you include only the name of the navigation property, you’ll receive all the properties for related records. You
can limit the properties returned for related records using the $select system query option in parentheses
after the navigation property name. Use this for both single-valued and collection-valued navigation properties.

NOTE
You are limited to no more than 10 $expand options in a query. This is to protect performance. Each $expand
options creates a join that can impact performance.
To retrieve related entities for an entity instance, see Retrieve related tables for a table by expanding navigation
properties.
Queries which expand collection-valued navigation properties may return cached data for those properties that
doesn’t reflect recent changes. It is recommended to use If-None-Match header with value null to override
browser caching. See HTTP Headers for more details.

Retrieve related table records by expanding single-valued navigation


properties
The following example demonstrates how to retrieve the contact for all the account records. For the related
contact records, we are only retrieving the contactid and fullname.
Request

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$expand=primarycontactid($select=contactid,fullname) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.1/$metadata#accounts(name,primarycontactid,primarycontactid(contactid,fullname))",
"value":[
{
"@odata.etag":"W/\"513475\"",
"name":"Fourth Coffee (sample)",
"accountid":"36dbf27c-8efb-e511-80d2-00155db07c77",
"primarycontactid":{
"contactid":"9cdbf27c-8efb-e511-80d2-00155db07c77",
"fullname":"Yvonne McKay (sample)"
}
},
{
"@odata.etag":"W/\"513477\"",
"name":"Litware, Inc. (sample)",
"accountid":"38dbf27c-8efb-e511-80d2-00155db07c77",
"primarycontactid":{
"contactid":"9edbf27c-8efb-e511-80d2-00155db07c77",
"fullname":"Susanna Stubberod (sample)"
}
}
]
}

Instead of returning the related entities for entity sets, you can also return references (links) to the related
entities by expanding the single-valued navigation property with the $ref option. The following example
returns links to the contact records for all the accounts.
Request

GET [Organization URI]/api/data/v9.1/accounts?$select=name


&$expand=primarycontactid/$ref HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#accounts(name,primarycontactid)",
"value":[
{
"@odata.etag":"W/\"513475\"",
"name":"Fourth Coffee (sample)",
"_primarycontactid_value":"9cdbf27c-8efb-e511-80d2-00155db07c77",
"accountid":"36dbf27c-8efb-e511-80d2-00155db07c77",
"primarycontactid":{
"@odata.id":"[Organization URI]/api/data/v9.1/contacts(9cdbf27c-8efb-e511-80d2-00155db07c77)"
}
},
{
"@odata.etag":"W/\"513477\"",
"name":"Litware, Inc. (sample)",
"_primarycontactid_value":"9edbf27c-8efb-e511-80d2-00155db07c77",
"accountid":"38dbf27c-8efb-e511-80d2-00155db07c77",
"primarycontactid":{
"@odata.id":"[Organization URI]/api/data/v9.1/contacts(9edbf27c-8efb-e511-80d2-00155db07c77)"
}
}
]
}

Multi-level expand of single-valued navigation properties


You can expand single-valued navigation properties to multiple levels by nesting an $expand option within
another $expand option.

NOTE
There is no limit on the depth of nested $expand options, but the combined limit of 10 total $expand options in a
query still applies.

The following query returns task records and expands the related contact , the account related to the
contact , and finally to the systemuser who created the account record.

Request

GET [Organization URI]/api/data/v9.1/tasks?$select=subject


&$expand=regardingobjectid_contact_task($select=fullname;
$expand=parentcustomerid_account($select=name;
$expand=createdby($select=fullname))) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.1/$metadata#tasks(subject,regardingobjectid_contact_task(fullname,parentcustomerid_account(
name,createdby(fullname))))",
"value":
[
{
"@odata.etag": "W/\"28876997\"",
"subject": "Task 1 for Susanna Stubberod",
"activityid": "834814f9-b0b8-ea11-a812-000d3a122b89",
"regardingobjectid_contact_task": {
"fullname": "Susanna Stubberod (sample)",
"contactid": "824814f9-b0b8-ea11-a812-000d3a122b89",
"parentcustomerid_account": {
"name": "Contoso, Ltd. (sample)",
"accountid": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"createdby": {
"fullname": "Nancy Anderson",
"systemuserid": "4026be43-6b69-e111-8f65-78e7d1620f5e",
"ownerid": "4026be43-6b69-e111-8f65-78e7d1620f5e"
}
}
}
},
{
"@odata.etag": "W/\"28877001\"",
"subject": "Task 2 for Susanna Stubberod",
"activityid": "844814f9-b0b8-ea11-a812-000d3a122b89",
"regardingobjectid_contact_task": {
"fullname": "Susanna Stubberod (sample)",
"contactid": "824814f9-b0b8-ea11-a812-000d3a122b89",
"parentcustomerid_account": {
"name": "Contoso, Ltd. (sample)",
"accountid": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"createdby": {
"fullname": "Nancy Anderson",
"systemuserid": "4026be43-6b69-e111-8f65-78e7d1620f5e",
"ownerid": "4026be43-6b69-e111-8f65-78e7d1620f5e"
}
}
}
}
]
}

Retrieve related tables by expanding collection-valued navigation


properties
If you expand on collection-valued navigation parameters to retrieve related entities for entity sets, only one
level of depth is returned if there is data. Otherwise the collection will return an empty array.
In either case an @odata.nextLink property will be returned for the related entities. If you want to retrieve the
collection separately, you can use the value of the @odata.nextLink property with a new GET request to return
the required data.
The following example retrieves the tasks assigned to the top 2 account records. One has related tasks, the other
does not.
Request

GET [Organization URI]/api/data/v9.1/accounts?$top=2


&$select=name
&$expand=Account_Tasks($select=subject,scheduledstart) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.1/$metadata#accounts(name,Account_Tasks(subject,scheduledstart))",
"value": [
{
"@odata.etag": "W/\"37867294\"",
"name": "Contoso, Ltd. (sample)",
"accountid": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"Account_Tasks": [
{
"@odata.etag": "W/\"28876919\"",
"subject": "Task 1 for Contoso, Ltd.",
"scheduledstart": null,
"_regardingobjectid_value": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"activityid": "7b4814f9-b0b8-ea11-a812-000d3a122b89"
},
{
"@odata.etag": "W/\"28876923\"",
"subject": "Task 2 for Contoso, Ltd.",
"scheduledstart": null,
"_regardingobjectid_value": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"activityid": "7c4814f9-b0b8-ea11-a812-000d3a122b89"
},
{
"@odata.etag": "W/\"28876927\"",
"subject": "Task 3 for Contoso, Ltd.",
"scheduledstart": null,
"_regardingobjectid_value": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"activityid": "7d4814f9-b0b8-ea11-a812-000d3a122b89"
}
],
"[email protected]": "[Organization URI]/api/data/v9.1/accounts(7a4814f9-b0b8-ea11-
a812-000d3a122b89)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag": "W/\"37526208\"",
"name": "Fourth Coffee",
"accountid": "ccd685f9-cddd-ea11-a813-000d3a122b89",
"Account_Tasks": [],
"[email protected]": "[Organization URI]/api/data/v9.1/accounts(ccd685f9-cddd-ea11-
a813-000d3a122b89)/Account_Tasks?$select=subject,scheduledstart"
}
]
}

Retrieve related tables by expanding both single-valued and


collection-valued navigation properties
The following example demonstrates how you can expand related entities for entity sets using both single and
collection-valued navigation properties. As explained earlier, expanding on collection-valued navigation
properties to retrieve related entities for entity sets returns one level of depth and an @odata.nextLink property
for the related entities.
In this example, we are retrieving the contact and tasks assigned to the top 2 accounts.
Request

GET [Organization URI]/api/data/v9.1/accounts?$top=2


&$select=name
&$expand=primarycontactid($select=contactid,fullname),
Account_Tasks($select=subject,scheduledstart) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.1/$metadata#accounts(name,primarycontactid(contactid,fullname),Account_Tasks(subject,schedu
ledstart))",
"value": [
{
"@odata.etag": "W/\"37867294\"",
"name": "Contoso, Ltd. (sample)",
"accountid": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"primarycontactid": {
"contactid": "7e4814f9-b0b8-ea11-a812-000d3a122b89",
"fullname": "Yvonne McKay (sample)"
},
"Account_Tasks": [
{
"@odata.etag": "W/\"28876919\"",
"subject": "Task 1 for Contoso, Ltd.",
"scheduledstart": null,
"_regardingobjectid_value": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"activityid": "7b4814f9-b0b8-ea11-a812-000d3a122b89"
},
{
"@odata.etag": "W/\"28876923\"",
"subject": "Task 2 for Contoso, Ltd.",
"scheduledstart": null,
"_regardingobjectid_value": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"activityid": "7c4814f9-b0b8-ea11-a812-000d3a122b89"
},
{
"@odata.etag": "W/\"28876927\"",
"subject": "Task 3 for Contoso, Ltd.",
"scheduledstart": null,
"_regardingobjectid_value": "7a4814f9-b0b8-ea11-a812-000d3a122b89",
"activityid": "7d4814f9-b0b8-ea11-a812-000d3a122b89"
}
],
"[email protected]": "[Organization URI]/api/data/v9.1/accounts(7a4814f9-b0b8-ea11-
a812-000d3a122b89)/Account_Tasks?$select=subject,scheduledstart"
},
{
"@odata.etag": "W/\"37526208\"",
"name": "Fourth Coffee",
"accountid": "ccd685f9-cddd-ea11-a813-000d3a122b89",
"primarycontactid": {
"contactid": "384d0f84-7de6-ea11-a817-000d3a122b89",
"fullname": "Charlie Brown"
},
"Account_Tasks": [],
"[email protected]": "[Organization URI]/api/data/v9.1/accounts(ccd685f9-cddd-ea11-
a813-000d3a122b89)/Account_Tasks?$select=subject,scheduledstart"
}
]
}

Filter collection values based on data in related tables


The Web API allows you to use two lambda operators, which are any and all to evaluate a Boolean
expression on a collection. More information: Use Lambda operators.

See also
Search across table data using Dataverse search
Query data using Web API
Perform operations using the Web API
Compose Http requests and handle errors
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Retrieve and execute predefined queries
5/21/2021 • 5 minutes to read • Edit Online

Microsoft Dataverse provides a way for administrators to create system views that are available to all users.
Individual users can save the Advanced Find queries for re-use in the application. Both of these represent
predefined queries you can retrieve and execute using the Web API. You can also compose a query using
FetchXml and use that to retrieve data.

NOTE
Unlike queries using the OData syntax, data returned from pre-defined queries or fetchXml will not return properties with
null values. When the value is null , the property will not be included in the results.

When a query is returned using OData syntax, a record will include a property with a null value like so:

{
"@odata.etag": "W/\"46849433\"",
"name": "Contoso, Ltd. (sample)",
"accountnumber": null,
"accountid": "7a4814f9-b0b8-ea11-a812-000d3a122b89"
}

When retrieved using a pre-defined query or with FetchXml, the same record will not include the accountnumber
property because it is null , like so:

{
"@odata.etag": "W/\"46849433\"",
"name": "Contoso, Ltd. (sample)",
"accountid": "7a4814f9-b0b8-ea11-a812-000d3a122b89"
}

Predefined queries
Dataverse allows you to define, save, and execute two types of queries as listed here.

Q UERY T Y P E DESC RIP T IO N

Saved Quer y System-defined views for a table (entity). These views are
stored in the savedquery EntityType /. More information:
Customize table views

User Quer y Advanced Find searches saved by users for a table (entity).
These views are stored in the userquery EntityType /. More
information: UserQuery (saved view) table

Records for both of these types of entities contain the FetchXML definition for the data to return. You can query
the respective entity type to retrieve the primary key value. With the primary key value, you can execute the
query by passing the primary key value. For example, to execute the Active Accounts saved query, you must
first get the primary key using a query like this.
GET [Organization URI]/api/data/v9.0/savedqueries?$select=name,savedqueryid&$filter=name eq 'Active
Accounts'

You can then use the savedqueryid value and pass it as the value to the savedQuery parameter to the accounts
entity set.

GET [Organization URI]/api/data/v9.0/accounts?savedQuery=00000000-0000-0000-00aa-000010001002

Use the same approach to get the userqueryid and pass it as the value to the userQuery parameter to the entity
set that matches the corresponding returnedtypecode of the saved query.

GET [Organization URI]/api/data/v9.0/accounts?userQuery=121c6fd8-1975-e511-80d4-00155d2a68d1

Apply a query to any collection of the appropriate type


In addition to simply applying the saved query to the main entity set collection, you can also use a saved query
or user query to apply the same filtering on any collection of the appropriate type of entities. For example, if you
want to apply a query against just the entities related to a specific entity, you can apply the same pattern. For
example, the following URL will apply the Open Oppor tunities query against the opportunities related to a
specific account via the opportunity_parent_account collection-valued navigation property.

GET [Organization URI]/api/data/v9.0/accounts(8f390c24-9c72-e511-80d4-


00155d2a68d1)/opportunity_parent_account/?savedQuery=00000000-0000-0000-00aa-000010003001

Use custom FetchXML


FetchXML is a proprietary query language that provides capabilities to perform aggregation. More information:
Use FetchXML to query data
You can pass URL encoded FetchXML as a query to the entity set corresponding to the root entity of the query
using the fetchXml query string parameter to return the results from the Web API. For example, you can have
the following FetchXML that has account as the entity.

<fetch mapping='logical'>
<entity name='account'>
<attribute name='accountid'/>
<attribute name='name'/>
<attribute name='accountnumber'/>
</entity>
</fetch>

The URL encoded value of this FetchXML is as shown here.

%3Cfetch%20mapping%3D%27logical%27%3E%3Centity%20name%3D%27account%27%3E%3Cattribute%20name%3D%27accountid%2
7%2F%3E%3Cattribute%20name%3D%27name%27%2F%3E%3Cattribute%20name%3D%27accountnumber%27%2F%3E%3C%2Fentity%3E%
3C%2Ffetch%3E

Most programming languages include a function to URL encode a string. For example, in JavaScript you use the
encodeURI function. You should URL encode any request that you send to any RESTful web service. If you paste
a URL into the address bar of your browser it should URL encode the address automatically. The following
example shows a GET request using the FetchXML shown previously using the entity set path for accounts.
Request

GET [Organization URI]/api/data/v9.0/accounts?


fetchXml=%3Cfetch%20mapping%3D%27logical%27%3E%3Centity%20name%3D%27account%27%3E%3Cattribute%20name%3D%27ac
countid%27%2F%3E%3Cattribute%20name%3D%27name%27%2F%3E%3Cattribute%20name%3D%27accountnumber%27%2F%3E%3C%2Fe
ntity%3E%3C%2Ffetch%3E HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.0/$metadata#accounts(accountid,name)","value":[
{
"@odata.etag":"W/\"506678\"","accountid":"89390c24-9c72-e511-80d4-00155d2a68d1","name":"Fourth Coffee
(sample)", "accountnumber":"1234",
},{
"@odata.etag":"W/\"502172\"","accountid":"8b390c24-9c72-e511-80d4-00155d2a68d1","name":"Litware, Inc.
(sample)"
},{
"@odata.etag":"W/\"502174\"","accountid":"8d390c24-9c72-e511-80d4-00155d2a68d1","name":"Adventure
Works (sample)"
},{
"@odata.etag":"W/\"506705\"","accountid":"8f390c24-9c72-e511-80d4-00155d2a68d1","name":"Fabrikam, Inc.
(sample)"
},{
"@odata.etag":"W/\"506701\"","accountid":"91390c24-9c72-e511-80d4-00155d2a68d1","name":"Blue Yonder
Airlines (sample)"
},{
"@odata.etag":"W/\"502180\"","accountid":"93390c24-9c72-e511-80d4-00155d2a68d1","name":"City Power &
Light (sample)"
},{
"@odata.etag":"W/\"502182\"","accountid":"95390c24-9c72-e511-80d4-00155d2a68d1","name":"Contoso
Pharmaceuticals (sample)"
},{
"@odata.etag":"W/\"506704\"","accountid":"97390c24-9c72-e511-80d4-00155d2a68d1","name":"Alpine Ski
House (sample)"
},{
"@odata.etag":"W/\"502186\"","accountid":"99390c24-9c72-e511-80d4-00155d2a68d1","name":"A. Datum
Corporation (sample)"
},{
"@odata.etag":"W/\"502188\"","accountid":"9b390c24-9c72-e511-80d4-00155d2a68d1","name":"Coho Winery
(sample)"
},{
"@odata.etag":"W/\"504177\"","accountid":"0a3238d4-f973-e511-80d4-00155d2a68d1","name":"Litware, Inc."
}
]
}

NOTE
Properties with null values will not be included in results returned using FetchXml. In the example above, only the first
record returned has an accountnumber value.

Paging with FetchXML


With FetchXML you can apply paging by setting the page and count attributes of the fetch element. For
example, to set a query for accounts and limit the number of entities to 2 and to return just the first page, the
following fetchXML:

<fetch mapping="logical" page="1" count="2">


<entity name="account">
<attribute name="accountid" />
<attribute name="name" />
<attribute name="industrycode" />
<order attribute="name" />
</entity>
</fetch>

A paging cookie must be requested as an annotation. Set the odata.include-annotations preference to use (or
include) Microsoft.Dynamics.CRM.fetchxmlpagingcookie and a @Microsoft.Dynamics.CRM.fetchxmlpagingcookie
property will be returned with the result.

Use FetchXML within a batch request


The length of a URL in a GET request is limited. Including FetchXML as a parameter in the URL can reach this
limit. You can execute a $batch operation using a POST request as a way to move the FetchXML out of the URL
and into the body of the request where this limit will not apply. More information:Execute batch operations
using the Web API.

NOTE
Sending a GET request within a Batch allows for URLs up to 32768 characters in length. Much more than with a normal
GET request, but it isn't unlimited.

Example
Request

POST [Organization URI]/api/data/v9.0/$batch HTTP/1.1

Content-Type:multipart/mixed;boundary=batch_AAA123
Accept:application/json
OData-MaxVersion:4.0
OData-Version:4.0

--batch_AAA123
Content-Type: application/http
Content-Transfer-Encoding: binary

GET [Organization URI]/api/data/v9.0/accounts?


fetchXml=%3Cfetch%20mapping='logical'%3E%3Centity%20name='account'%3E%3Cattribute%20name='accountid'/%3E%3Ca
ttribute%20name='name'/%3E%3Cattribute%20name='telephone1'/%3E%3Cattribute%20name='accountid'/%3E%3Cattribut
e%20name='creditonhold'/%3E%3C/entity%3E%3C/fetch%3E HTTP/1.1
Content-Type: application/json
OData-Version: 4.0
OData-MaxVersion: 4.0

--batch_AAA123--

Response
--batchresponse_cbfd44cd-a322-484e-913b-49e18af44e34
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.0/$metadata#accounts(accountid,name,telephone1,creditonhold)",
"value":[
{
"@odata.etag":"W/\"563737\"",
"accountid":"1f55c679-485e-e811-8151-000d3aa3c22a",
"name":"Fourth Coffee (sample)",
"telephone1":"+1-425-555-0121",
"creditonhold":false
},
{
"@odata.etag":"W/\"563739\"",
"accountid":"2555c679-485e-e811-8151-000d3aa3c22a",
"name":"Litware, Inc. (sample)",
"telephone1":"+1-425-555-0120",
"creditonhold":false
}
]
}
--batchresponse_cbfd44cd-a322-484e-913b-49e18af44e34--

See also
Web API Query Data Sample (C#)
Web API Query Data Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Create a table row using the Web API
7/19/2021 • 6 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Use a POST request to send data to create a table row (entity record). You can create multiple related table rows
in a single operation using deep insert. You also need to know how to set values to associate a new table row to
existing tables using the @odata.bind annotation.

NOTE
For information about how to create and update the table (entity) definitions using the Web API, see Create and update
table definitions using the Web API.

Basic Create
This example creates a new account entity record. The response OData-EntityId header contains the Uri of the
created entity.
Request

POST [Organization URI]/api/data/v9.0/accounts HTTP/1.1


Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

{
"name": "Sample Account",
"creditonhold": false,
"address1_latitude": 47.639583,
"description": "This is the description of the sample account",
"revenue": 5000000,
"accountcategorycode": 1
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/accounts(7eb682f1-ca75-e511-80d4-00155d2a68d1)

To create a new entity record you must identify the valid property names and types. For all system entities and
attributes (table columns), you can find this information in the topic for that entity in the About the Table
Reference. For custom entities or attributes, refer to the definition of that entity in the CSDL $metadata
document. More information: Entity types
Create with data returned
You can compose your POST request so that data from the created record will be returned with a status of
201 (Created) . To get this result, you must use the return=representation preference in the request headers.

To control which properties are returned, append the $select query option to the URL to the entity set. You
may also use $expand to return related entities.
When an entity is created in this way the OData-EntityId header containing the URI to the created record is not
returned.
This example creates a new account entity and returns the requested data in the response.
Request

POST [Organization URI]/api/data/v9.0/accounts?


$select=name,creditonhold,address1_latitude,description,revenue,accountcategorycode,createdon HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
Prefer: return=representation

{
"name": "Sample Account",
"creditonhold": false,
"address1_latitude": 47.639583,
"description": "This is the description of the sample account",
"revenue": 5000000,
"accountcategorycode": 1
}

Response

HTTP/1.1 201 Created


Content-Type: application/json; odata.metadata=minimal
Preference-Applied: return=representation
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#accounts/$entity",
"@odata.etag": "W/\"536530\"",
"accountid": "d6f193fc-ce85-e611-80d8-00155d2a68de",
"accountcategorycode": 1,
"description": "This is the description of the sample account",
"address1_latitude": 47.63958,
"creditonhold": false,
"name": "Sample Account",
"createdon": "2016-09-28T22:57:53Z",
"revenue": 5000000.0000,
"_transactioncurrencyid_value": "048dddaa-6f7f-e611-80d3-00155db5e0b6"
}

Create related table rows in one operation


You can create entities related to each other by defining them as navigation properties values. This is known as
deep insert.
As with a basic create, the response OData-EntityId header contains the Uri of the created entity. The URIs for
the related entities created aren’t returned. You can get the primary key values of the records if you use the
Prefer: return=representation header so it returns the values of the created record. More information: Create
with data returned
For example, the following request body posted to the accounts entity set will create a total of four new entities
in the context of creating an account.
A contact is created because it is defined as an object property of the single-valued navigation property
primarycontactid .

An opportunity is created because it is defined as an object within an array that is set to the value of a
collection-valued navigation property opportunity_customer_accounts .
A task is created because it is defined an object within an array that is set to the value of a collection-
valued navigation property Opportunity_Tasks .

NOTE
When creating a new table row, it is not possible to combine the row creation with the insert of a non-primary image. For
a non-primary image to be added, the row must already exist.

Request

POST [Organization URI]/api/data/v9.0/accounts HTTP/1.1


Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

{
"name": "Sample Account",
"primarycontactid":
{
"firstname": "John",
"lastname": "Smith"
},
"opportunity_customer_accounts":
[
{
"name": "Opportunity associated to Sample Account",
"Opportunity_Tasks":
[
{ "subject": "Task associated to opportunity" }
]
}
]
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/accounts(3c6e4b5f-86f6-e411-80dd-00155d2a68cb)
Associate table rows on create
To associate new entities to existing entities when they are created you must set the value of navigation
properties using the @odata.bind annotation.
The following request body posted to the accounts entity set will create a new account associated with an
existing contact with the contactid value of 00000000-0000-0000-0000-000000000001 and two existing tasks with
activityid values of 00000000-0000-0000-0000-000000000002 and 00000000-0000-0000-0000-000000000003 .

NOTE
This request is using the Prefer: return=representation header so it returns the values of the created record. More
information: Create with data returned

Request

POST [Organization URI]/api/data/v9.0/accounts?


$select=name&$expand=primarycontactid($select=fullname),Account_Tasks($select=subject) HTTP/1.1
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Prefer: return=representation

{
"name": "Sample Account",
"[email protected]": "/contacts(00000000-0000-0000-0000-000000000001)",
"[email protected]": [
"/tasks(00000000-0000-0000-0000-000000000002)",
"/tasks(00000000-0000-0000-0000-000000000003)"
]
}

Response
HTTP/1.1 201 Created
OData-Version: 4.0
Preference-Applied: return=representation

{
"@odata.context": "[Organization
URI]/api/data/v9.1/$metadata#accounts(name,primarycontactid(fullname),Account_Tasks(subject))/$entity",
"@odata.etag": "W/\"36236432\"",
"name": "Sample Account",
"accountid": "00000000-0000-0000-0000-000000000004",
"primarycontactid": {
"@odata.etag": "W/\"28877094\"",
"fullname": "Yvonne McKay (sample)",
"contactid": "00000000-0000-0000-0000-000000000001"
},
"Account_Tasks": [
{
"@odata.etag": "W/\"36236437\"",
"subject": "Task 1",
"activityid": "00000000-0000-0000-0000-000000000002"
},
{
"@odata.etag": "W/\"36236440\"",
"subject": "Task 2",
"activityid": "00000000-0000-0000-0000-000000000003"
}
]
}

Check for duplicate records


By default, duplicate detection is suppressed when you are creating records using the Web API. You must include
the MSCRM.SuppressDuplicateDetection: false header with your POST request to enable duplicate detection.
Duplicate detection only applies when 1) the organization has enabled duplicate detection, 2) the entity is
enabled for duplicate detection, and 3) there are active duplicate detection rules being applied. More
information: Detect duplicate data using code
See Detect duplicate data using Web API for more information on how to check for duplicate records during
Create operation.

Create a new table row from another table


Use InitializeFrom function to create a new record in the context of an existing record where a mapping exists
between the entities to which the records belong.
The following example shows how to create an account record using the attribute values of an existing record of
account entity with accountid value equal to c65127ed-2097-e711-80eb-00155db75426 .
Request
GET [Organization
URI]/api/data/v9.0/InitializeFrom(EntityMoniker=@p1,TargetEntityName=@p2,TargetFieldType=@p3)?@p1=
{'@odata.id':'accounts(c65127ed-2097-e711-80eb-
00155db75426)'}&@p2='account'&@p3=Microsoft.Dynamics.CRM.TargetFieldType'ValidForCreate' HTTP/1.1
If-None-Match: null
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json
Accept: application/json

Response

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#accounts/$entity",
"@odata.type": "#Microsoft.Dynamics.CRM.account",
"[email protected]": "accounts(c65127ed-2097-e711-80eb-00155db75426)",
"[email protected]": "transactioncurrencies(732e87e1-1d96-e711-80e4-00155db75426)",
"address1_line1": "123 Maple St.",
"address1_city": "Seattle",
"address1_country": "United States of America"
}

The response received from InitializeFrom request consists of values of mapped attributes between the source
entity and target entity and the GUID of the parent record. The attribute mapping between entities that have an
entity relationship is different for different entity sets and is customizable, so the response from InitializeFrom
function request may vary for different entities and organizations. When this response is passed in the body of
create request of the new record, these attribute values are replicated in the new record. The values of custom
mapped attributes also get set in the new record during the process.

NOTE
To determine whether two entities can be mapped, use this query:
GET [Organization URI]/api/data/v9.1/entitymaps?
$select=sourceentityname,targetentityname&$orderby=sourceentityname

Other attribute values can also be set and/or modified for the new record by adding them in the JSON request
body, as shown in the example below.

POST [Organization URI]/api/data/v9.0/accounts HTTP/1.1


Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#accounts/$entity",
"@odata.type": "#Microsoft.Dynamics.CRM.account",
"[email protected]": "accounts(c65127ed-2097-e711-80eb-00155db75426)",
"[email protected]": "transactioncurrencies(732e87e1-1d96-e711-80e4-00155db75426)",
"name":"Contoso Ltd",
"numberofemployees":"200",
"address1_line1":"100 Maple St.",
"address1_city":"Seattle",
"address1_country":"United States of America",
"fax":"73737"
}
}
Create documents in storage partitions
If you are creating large numbers of entities that contain documents, you can create the entities in storage
partitions to speed up access to those entity records.
More information: Access documents faster using storage partitions
See also
Web API Basic Operations Sample (C#)
Web API Basic Operations Sample (Client-side JavaScript)
InitializeFrom Function
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Retrieve a table row using the Web API
5/21/2021 • 9 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Use a GET request to retrieve data for a table specified as the resource with a unique identifier. When retrieving
a table row (entity record) you can also request specific properties and expand navigation properties to return
properties from related tables.

NOTE
For information about retrieving table definitions, see Query table definitions using the Web API.

Basic retrieve example


This example returns data for an account entity record with the primary key value equal to 00000000-0000-
0000-0000-000000000001.

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)

To retrieve more than one entity record at a time, see Basic query example in the Query Data using the Web API
topic.
Cau t i on

The above example will return all the properties for account record, which is not a performance best practice for
retrieving data. This example was just to illustrate how you can do a basic retrieve of an entity record in
Microsoft Dataverse. Because all the properties were returned, we haven't included the response information for
the request in this example.
As a performance best practice, you must always use the $select system query option to limit the properties
returned while retrieving data. See the following section, Retrieve specific proper ties , for information about
this.

Retrieve specific properties


Use the $select system query option to limit the properties returned by including a comma-separated list of
property names. This is an important performance best practice. If properties aren’t specified using $select , all
properties will be returned.
The following example retrieves name and revenue properties for the account entity with the primary key value
equal to 00000000-0000-0000-0000-000000000001
Request
GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)?$select=name,revenue
HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#accounts(name,revenue)/$entity",
"@odata.etag": "W/\"502186\"",
"name": "A. Datum Corporation (sample)",
"revenue": 10000,
"accountid": "00000000-0000-0000-0000-000000000001",
"_transactioncurrencyid_value":"b2a6b689-9a39-e611-80d2-00155db44581"
}

When you request certain types of properties you can expect additional read-only properties to be returned
automatically.
If you request a money value, the _transactioncurrencyid_value lookup property will be returned. This property
contains only the GUID value of the transaction currency so you could use this value to retrieve information
about the currency using the transactioncurrency EntityType /. Alternatively, by requesting annotations you can
also get additional data in the same request. More information:Retrieve data about lookup properties
If you request a property that is part of a composite attribute for an address, you will get the composite
property as well. For example, if your query requests the address1_line1 property for a contact, the
address1_composite property will be returned as well.

Retrieve using an alternate key


If an entity has an alternate key defined, you can also use the alternate key to retrieve the entity instead of the
unique identifier for the entity. For example, if the Contact entity has an alternate key definition that includes
both the firstname and emailaddress1 properties, you can retrieve the contact using a query with data
provided for those keys as shown here.

GET [Organization URI]/api/data/v9.0/contacts(firstname='Joe',emailaddress1='[email protected]')

If the alternate key definition contains lookup type field (for example, the primarycontactid property for the
account entity), you can retrieve the account using the lookup property as shown here.

GET [Organization URI]/api/data/v9.0/accounts(_primarycontactid_value=00000000-0000-0000-0000-000000000001)

Any time you need to uniquely identify an entity to retrieve, update, or delete, you can use alternate keys
configured for the entity. By default, there are no alternate keys configured for entities. Alternate keys will only
be available if the organization or a solution adds them.
Retrieve documents in storage partitions
If you are retrieving entity data stored in partitions be sure to specify the partition key when retrieving that data.
More information: Access table data faster using storage partitions

Retrieve a single property value


When you only need to retrieve the value of a single property for an entity, you can append the name of the
property to the URI for an entity to return only the value for that property. This is a performance best practice
because less data needs to be returned in the response.
This example returns only the value of the name property for an account entity.
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)/name HTTP/1.1


Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.0/$metadata#accounts(00000000-0000-0000-0000-
000000000001)/name",
"value":"Adventure Works (sample)"
}

Retrieve navigation property values


In the same way that you can retrieve individual property values, you can also access the values of navigation
properties (lookup fields) by appending the name of the navigation property to the URI referencing an
individual entity.
The following example returns the fullname of the primary contact of an account using the
primarycontactid single-valued navigation property.

Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)/primarycontactid?


$select=fullname HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#contacts(fullname)/$entity",
"@odata.etag": "W/\"500128\"",
"fullname": "Rene Valdes (sample)",
"contactid": "ff390c24-9c72-e511-80d4-00155d2a68d1"
}

For collection-valued navigation properties you have the option to request to return only references to the
related entities or just a count of the related entities.
The following example will just return references to tasks related to a specific account by adding /$ref to the
request.
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)/AccountTasks/$ref


HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#Collection($ref)",
"value":
[
{ "@odata.id": "[Organization URI]/api/data/v9.0/tasks(6b5941dd-d175-e511-80d4-00155d2a68d1)" },
{ "@odata.id": "[Organization URI]/api/data/v9.0/tasks(fcbb60ed-d175-e511-80d4-00155d2a68d1)" }
]
}

The following example returns the number of tasks related to a specific account using the Account_Tasks
collection-valued navigation property with /$count appended.
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)/Account_Tasks/$count


HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

2
NOTE
The value returned includes the UTF-8 byte order mark (BOM) characters (  ) that represent that this is a UTF-8
document.

Retrieve related tables for a table by expanding navigation properties


Use the $expand system query option to control what data from related entities is returned. There are two types
of navigation properties:
Single-valued navigation properties correspond to Lookup attributes that support many-to-one relationships
and allow setting a reference to another entity.
Collection-valued navigation properties correspond to one-to-many or many-to-many relationships.
If you simply include the name of the navigation property, you’ll receive all the properties for related records.
You can limit the properties returned for related records using the $select system query option in parentheses
after the navigation property name. Use this for both single-valued and collection-valued navigation properties.

NOTE
To retrieve related entities for entity sets, see Retrieve related tables by expanding navigation properties.

Retrieve related entities for an entity instance by expanding single-valued navigation


proper ties :
The following example demonstrates how to retrieve the contact for an account entity. For the related
contact record, we are only retrieving the contactid and fullname.
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)?


$select=name&$expand=primarycontactid($select=contactid,fullname) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.0/$metadata#accounts(name,primarycontactid,primarycontactid(contactid,fullname))/$en
tity",
"@odata.etag":"W/\"550616\"",
"name":"Adventure Works (sample)",
"accountid":"00000000-0000-0000-0000-000000000001",
"primarycontactid":
{
"@odata.etag":"W/\"550626\"",
"contactid":"c59648c3-68f7-e511-80d3-00155db53318",
"fullname":"Nancy Anderson (sample)"
}
}
Instead of returning the related entities for entity records, you can also return references (links) to the
related entities by expanding the single-valued navigation property with the $ref option. The following
example returns links to the contact record for the account entity.
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)?


$select=name&$expand=primarycontactid/$ref HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.0/$metadata#accounts(name,primarycontactid)/$entity",
"@odata.etag":"W/\"550616\"",
"name":"Adventure Works (sample)",
"accountid":"00000000-0000-0000-0000-000000000001",
"_primarycontactid_value":"c59648c3-68f7-e511-80d3-00155db53318",
"primarycontactid": { "@odata.id":"[Organization URI]/api/data/v9.0/contacts(c59648c3-68f7-e511-
80d3-00155db53318)" }
}

Retrieve related entities for an entity instance by expanding collection-valued navigation


proper ties :
The following example demonstrates how you can retrieve all the tasks assigned to an account record.
Request

GET [Organization URI]/api/data/v9.0/accounts(915e89f5-29fc-e511-80d2-00155db07c77)?$select=name


&$expand=Account_Tasks($select=subject,scheduledstart)
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#accounts(name,Account_Tasks,Account_Tasks(subject,scheduledstart))/$enti
ty",
"@odata.etag": "W/\"514069\"",
"name": "Sample Child Account 1",
"accountid": "915e89f5-29fc-e511-80d2-00155db07c77",
"Account_Tasks":
[
{
"@odata.etag": "W/\"514085\"",
"subject": "Sample Task 1",
"scheduledstart": "2016-04-11T15:00:00Z",
"activityid": "a983a612-3ffc-e511-80d2-00155db07c77"
},
{
"@odata.etag": "W/\"514082\"",
"subject": "Sample Task 2",
"scheduledstart": "2016-04-13T15:00:00Z",
"activityid": "7bcc572f-3ffc-e511-80d2-00155db07c77"
}
]
}

NOTE
If you expand on collection-valued navigation parameters to retrieve related entities for entity sets, a @odata.nextLink
property will be returned instead for the related entities. You should use the value of the @odata.nextLink property with a
new GET request to return the required data. More information:Retrieve related tables by expanding navigation properties

Retrieve related entities for an entity instance by expanding both single-valued and
collection-valued navigation proper ties : The following example demonstrates how you can expand
related entities for an entity instance using both single- and collection-values navigation properties.
Request

GET [Organization URI]/api/data/v9.0/accounts(99390c24-9c72-e511-80d4-00155d2a68d1)?$select=accountid


&$expand=parentaccountid($select%20=%20createdon,%20name),Account_Tasks($select%20=%20subject,%20sche
duledstart) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#accounts(accountid,parentaccountid,Account_Tasks,parentaccountid(created
on,name),Account_Tasks(subject,scheduledstart))/$entity",
"@odata.etag": "W/\"514069\"",
"accountid": "915e89f5-29fc-e511-80d2-00155db07c77",
"parentaccountid":
{
"@odata.etag": "W/\"514074\"",
"createdon": "2016-04-06T00:29:04Z",
"name": "Adventure Works (sample)",
"accountid": "3adbf27c-8efb-e511-80d2-00155db07c77"
},
"Account_Tasks":
[
{
"@odata.etag": "W/\"514085\"",
"subject": "Sample Task 1",
"scheduledstart": "2016-04-11T15:00:00Z",
"activityid": "a983a612-3ffc-e511-80d2-00155db07c77"
},
{
"@odata.etag": "W/\"514082\"",
"subject": "Sample Task 2",
"scheduledstart": "2016-04-13T15:00:00Z",
"activityid": "7bcc572f-3ffc-e511-80d2-00155db07c77"
}
]
}

NOTE
You can’t use the /$ref or /$count path segments to return only the URI for the related entity or a count of the
number of related entities.

Options to apply to expanded tables


You can apply certain system query options on the entities returned for a collection-valued navigation property.
Use a semicolon-separated list of system query options enclosed in parentheses after the name of the
collection-valued navigation property. You can use $select , $filter , $orderby , $top , and $expand .
The following example filters the results of task entities related to an account to those with a subject that ends
with “1.”

?$expand=Account_Tasks($filter=endswith(subject,'1');$select=subject)

The following example specifies that related tasks should be returned in ascending order based on the
createdon property.

?$expand=Account_Tasks($orderby=createdon asc;$select=subject,createdon)

The following example returns only the first related task.


?$expand=Account_Tasks($top=1;$select=subject)

The following example applies nested $expand options to return details about the systemuser who last
modified the account and the name of the businessunit that user belongs to.

?$select=name&$expand=modifiedby($select=fullname;$expand=businessunitid($select=name))

NOTE
Nested $expand options can only be applied to single-valued navigation properties.
Each request can include a maximum of 10 $expand options. There is no limit on the depth of nested $expand
options, but the limit of 10 total $expand options applies to these as well.
This is a subset of the system query options described in the “11.2.4.2.1 Expand Options” section of OData
Version 4.0 Part 1: Protocol Plus Errata 02. The options $skip , $count , $search , and $levels aren’t
supported for the Web API.

More information about nested $expand option use: Multi-level expand of single-valued navigation properties

Detect if a table has changed since it was retrieved


As a performance best practice you should only request data that you need. If you have previously retrieved an
entity record, you can use the ETag associated with a previously retrieved record to perform conditional
retrievals on that record. More information: Conditional retrievals.

Retrieve formatted values


Requesting formatted values for individual record retrievals is done the same way as done when querying entity
sets. More information: Include formatted values.
See also
Web API Basic Operations Sample (C#)
Web API Basic Operations Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Update and delete table rows using the Web API
5/28/2021 • 5 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Operations to modify data are a core part of the Web API. In addition to a simple update and delete, you can
perform operations on single table columns (entity attributes) and compose upsert requests that will either
update or insert data depending on whether it exists.

Basic update
Update operations use the HTTP PATCH verb. Pass a JSON object containing the properties you want to update
to the URI that represents the entity. A response with a status of 204 will be returned if the update is successful.
The If-Match: * header helps ensure you don't create a new record by accidentally performing an upsert
operation. More information: Prevent create in upsert.

IMPORTANT
When updating an entity, only include the properties you are changing in the request body. Simply updating the
properties of an entity that you previously retrieved, and including that JSON in your request, will update each property
even though the value is the same. This can cause system events that can trigger business logic that expects that the
values have changed. This can cause properties to appear to have been updated in auditing data when in fact they
haven’t actually changed.

NOTE
The definition for attributes includes a RequiredLevel property. When this is set to SystemRequired , you cannot set
these attributes to a null value. More information: Attribute requirement level

This example updates an existing account record with the accountid value of 00000000-0000-0000-0000-
000000000001.
Request
PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
If-Match: *

{
"name": "Updated Sample Account ",
"creditonhold": true,
"address1_latitude": 47.639583,
"description": "This is the updated description of the sample account",
"revenue": 6000000,
"accountcategorycode": 2
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

NOTE
See Associate and disassociate tables on update for information about associating and disassociating entities on update.

Update with data returned


To retrieve data from an entity you are updating you can compose your PATCH request so that data from the
created record will be returned with a status of 200 (OK). To get this result, you must use the
return=representation preference in the request headers.

To control which properties are returned, append the $select query option to the URL to the entity set. The
$expand query option will be ignored if used.

This example updates an account entity and returns the requested data in the response.
Request

PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)?


$select=name,creditonhold,address1_latitude,description,revenue,accountcategorycode,createdon HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
Prefer: return=representation
If-Match: *

{"name":"Updated Sample Account"}

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
Preference-Applied: return=representation
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#accounts/$entity",
"@odata.etag": "W/\"536537\"",
"accountid": "00000000-0000-0000-0000-000000000001",
"accountcategorycode": 1,
"description": "This is the description of the sample account",
"address1_latitude": 47.63958,
"creditonhold": false,
"name": "Updated Sample Account",
"createdon": "2016-09-28T23:14:00Z",
"revenue": 5000000.0000,
"_transactioncurrencyid_value": "048dddaa-6f7f-e611-80d3-00155db5e0b6"
}

Update a single property value


When you want to update only a single property value use a PUT request with the property name appended to
the Uri of the entity.
The following example updates the name property of an existing account entity with the accountid value of
00000000-0000-0000-0000-000000000001.
Request

PUT [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)/name HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{"value": "Updated Sample Account Name"}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Delete a single property value


To delete the value of a single property use a DELETE request with the property name appended to the Uri of the
entity.
The following example deletes the value of the description property of an account entity with the accountid
value of 00000000-0000-0000-0000-000000000001.
Request
DELETE [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)/description HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

NOTE
This can’t be used with a single-valued navigation property to disassociate two entities. For an alternative approach, see
Remove a reference to a table.

Upsert a table
An upsert operation is exactly like an update. It uses a PATCH request and uses a URI to reference a specific
entity. The difference is that if the entity doesn’t exist it will be created. If it already exists, it will be updated.
Normally when creating a new entity you will let the system assign a unique identifier. This is a best practice. But
if you need to create a record with a specific id value, an upsert operation provides a way to do this. This can
be valuable in situation where you are synchronizing data in different systems.
Sometimes there are situations where you want to perform an upsert , but you want to prevent one of the
potential default actions: either create or update. You can accomplish this through the addition of If-Match or
If-None-Match headers. For more information, see Limit upsert operations.

Basic delete
A delete operation is very straightforward. Use the DELETE verb with the URI of the entity you want to delete.
This example message deletes an account entity with the primary key accountid value equal to 00000000-
0000-0000-0000-000000000001.
Request

DELETE [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
If the entity exists, you’ll get a normal response with status 204 to indicate the delete was successful. If the entity
isn’t found, you’ll get a response with status 404.

HTTP/1.1 204 No Content


OData-Version: 4.0
Check for duplicate records
See Detect duplicates during Update operation using the Web API for more information on how to check for
duplicate records during Update operation.

Update and delete documents in storage partitions


If you are updating or deleting entity data stored in partitions be sure to specify the partition key when
accessing that data.
More information: Access table data faster using storage partitions
See also
Web API Basic Operations Sample (C#)
Web API Basic Operations Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Associate and disassociate table rows using the Web
API
5/25/2021 • 3 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

There are several methods you can use to associate and disassociate tables (entities). Which method you apply
depends on whether you’re creating or updating the tables and whether you’re operating in the context of the
referenced table or the referencing table.

Add a reference to a collection-valued navigation property


The following example shows how to associate an existing opportunity with the opportunityid value of
00000000-0000-0000-0000-000000000001 to the collection-valued opportunity_customer_accounts navigation
property for an account with the accountid value of 00000000-0000-0000-0000-000000000002 . This is a 1:N
relationship but you can perform the same operation for an N:N relationship.
Request

POST [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-


000000000002)/opportunity_customer_accounts/$ref HTTP/1.1
Content-Type: application/json
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"@odata.id":"[Organization URI]/api/data/v9.0/opportunities(00000000-0000-0000-0000-000000000001)"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Change the reference in a single-valued navigation property


You can associate rows by setting the value of a single-valued navigation property using PUT request with the
following pattern.
Request
PUT [Organization URI]/api/data/v9.0/opportunities(00000000-0000-0000-0000-
000000000001)/customerid_account/$ref HTTP/1.1
Content-Type: application/json
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"@odata.id":"accounts(00000000-0000-0000-0000-000000000002)"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Remove a reference to a table row


Use a DELETE request to remove a reference to a row. The way you do it is different depending on whether
you’re referring to a collection-valued navigation property or a single-valued navigation property.
Request
For a collection-valued navigation property, use the following.

DELETE [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-


000000000002)/opportunity_customer_accounts/$ref?$id=[Organization
URI]/api/data/v9.0/opportunities(00000000-0000-0000-0000-000000000001) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Or, use this.

DELETE [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-


000000000002)/opportunity_customer_accounts(00000000-0000-0000-0000-000000000001)/$ref HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Request
For a single-valued navigation property, remove the $id query string parameter.

DELETE [Organization URI]/api/data/v9.0/opportunities(00000000-0000-0000-0000-


000000000001)/customerid_account/$ref HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
Either way, a successful response has status 204.

HTTP/1.1 204 No Content


OData-Version: 4.0
Associate table rows on create
As described in Associate rows on create, you can associate the new row to existing rows by setting the
navigation properties using the @odata.bind annotation.
As described in Create related tables in one operation, new tables can be created with relationships using deep
insert.

Associate and disassociate table rows on update


You can set the value of single-valued navigation properties using PATCH to associate or disassociate rows.

Associate table rows on update


You can associate rows on update using the same message described in Basic update but you must use the
@odata.bind annotation to set the value of a single-valued navigation property. The following example changes
the account associated to an opportunity using the customerid_account single-valued navigation property.
Request

PATCH [Organization URI]/api/data/v9.0/opportunities(00000000-0000-0000-0000-000000000001) HTTP/1.1


Content-Type: application/json
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"[email protected]":"accounts(00000000-0000-0000-0000-000000000002)"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Disassociate table rows on update


You can remove a reference to a single-valued navigation property when updating by setting the value to null .
This method allows you to disassociate multiple references in a single operation. There are two ways to do this:
You can set the value to null using the @odata.bind annotation:

PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1


Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"[email protected]": null,
"[email protected]": null
}

Or, just use the name of the single-valued navigation property.


PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"parentaccountid": null,
"primarycontactid": null
}

More information: Basic update

Associate table rows on update using collection-valued navigation


property
The following example shows how to associate multiple existing ActivityParty with an Email using collection-
valued navigation property email_activity_parties .

NOTE
Associating multiple tables with a table on update is a special scenario that is possible only with activityparty EntityType /.

Request

PUT [Organization URI]/api/data/v9.0/emails(2479d20d-3a39-e711-8145-e0071b6a2001)/email_activity_parties


Content-Type: application/json
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"value": [
{
"[email protected]":"contacts(a30d4045-fc46-e711-8115-e0071b66df51)",
"participationtypemask":3

},
{
"[email protected]":"contacts(1dcdda07-3a39-e711-8145-e0071b6a2001)",
"participationtypemask":2

}
]
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

See also
Web API Basic Operations Sample (C#)
Web API Basic Operations Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Merge table rows using the Web API
5/21/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

When you find duplicate records you can combine them into one using the Merge Action.

NOTE
Only the following entity types can be merged:
account
contact
incident
lead

Merge action
Merge is an unbound action that accepts four parameters:

NAME TYPE DESC RIP T IO N

Target crmbaseentity The target of the merge operation.

Subordinate crmbaseentity The entity record from which to merge


data.

UpdateContent crmbaseentity Additional entity attributes to be set


during the merge operation.

PerformParentingChecks Boolean Indicates whether to check if the


parent information is different for the
two entity records.

All the parameters are required.


Use a POST request to send data to merge entities. This example merges two account entity records while
updating accountnumber property of the record that will remain after the merge.
Request
POST [Organization URI]/api/data/v9.0/Merge HTTP/1.1
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

{
"Target": {
"name": "Account 1",
"accountid": "bb8055c0-aea6-ea11-a812-000d3a55d474",
"@odata.type": "Microsoft.Dynamics.CRM.account"
},
"Subordinate": {
"name": "Account 2",
"accountid": "c38055c0-aea6-ea11-a812-000d3a55d474",
"@odata.type": "Microsoft.Dynamics.CRM.account"
},
"UpdateContent": {
"accountnumber": "1234",
"@odata.type": "Microsoft.Dynamics.CRM.account"
},
"PerformParentingChecks": false
}

IMPORTANT
Because the Target , Subordinate , and UpdateContent property types are not explicitly defined by the parameter,
you must include the @odata.type annotation to specify the type.

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

See also
Merge duplicate records
MergeRequest
Use Web API functions
5/21/2021 • 6 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Functions and actions represent re-usable operations you can perform using the Web API. There are two types
of functions in the Web API:
Functions
Use a GET request with functions listed in Web API Function Reference to perform operations that have no side-
effects. These functions generally retrieve data. They return either a collection or a complex type. Each of these
functions has a corresponding message in the organization service.
Quer y Functions
Use the functions listed in Web API Query Function Reference to evaluate properties and values in the
composition of a query. Each of these functions has a corresponding ConditionOperator value.

Passing parameters to a function


For those functions that require parameters, the best practice is to pass the values using parameters. For
example, when you use the GetTimeZoneCodeByLocalizedName Function /, you must include the
LocalizedStandardName and LocaleId parameter values. So, you could use the following inline syntax as shown
here.

GET [Organization URI]/api/data/v9.0/GetTimeZoneCodeByLocalizedName(LocalizedStandardName='Pacific Standard


Time',LocaleId=1033)

However, there’s an open issue using DateTimeOffset values with the inline syntax, as explained in the following
article: DateTimeOffset as query parameter #204.
Therefore, the best practice is to pass the values in as parameters as shown in the following code sample. If you
use this best practice, you can avoid the open issue that applies to DateTimeOffset .

GET [Organization URI]/api/data/v9.0/GetTimeZoneCodeByLocalizedName(LocalizedStandardName=@p1,LocaleId=@p2)?


@p1='Pacific Standard Time'&@p2=1033

Parameter aliases also allow you to re-use parameter values to reduce the total length of the URL when the
parameter value is used multiple times.

Pass reference to a table to a function


Certain functions will require passing a reference to an existing entity. For example, the following functions have
a parameter that requires a crmbaseentity EntityType/:
F UN C T IO N S F UN C T IO N S ( C O N T 'D) F UN C T IO N S ( C O N T 'D)

CalculateRollupField Function/ IncrementKnowledgeArticleViewCount InitializeFrom Function/


Function/

IsValidStateTransition Function/ RetrieveDuplicates Function/ RetrieveLocLabels Function/

RetrievePrincipalAccess Function/ RetrieveRecordWall Function/ ValidateRecurrenceRule Function/

When you pass a reference to an existing entity, use the @odata.id annotation to the Uri for the entity. For
example if you are using the RetrievePrincipalAccess Function / you can use the following Uri to specify
retrieving access to a specific contact:

GET [Organization URI]/api/data/v9.0/systemusers(af9b3cf6-f654-4cd9-97a6-


cf9526662797)/Microsoft.Dynamics.CRM.RetrievePrincipalAccess(Target=@tid)?@tid=
{'@odata.id':'contacts(9f3162f6-804a-e611-80d1-00155d4333fa)'}

The @odata.id annotation can be the full Uri, but a relative Uri works too.

Bound and unbound functions


Only those functions found in Web API Function Reference may be bound. Query functions are never bound.

Bound functions
In the CSDL metadata document, when a Function element represents a bound function, it has an IsBound
attribute with the value true . The first Parameter element defined in the function represents the entity that the
function is bound to. When the Type attribute of the parameter is a collection, the function is bound to an entity
collection. As an example, the following is the definition of the CalculateTotalTimeIncident Function / and
CalculateTotalTimeIncidentResponse ComplexType / in the CSDL.

<ComplexType Name="CalculateTotalTimeIncidentResponse">
<Property Name="TotalTime" Type="Edm.Int64" Nullable="false" />
</ComplexType>
<Function Name="CalculateTotalTimeIncident" IsBound="true">
<Parameter Name="entity" Type="mscrm.incident" Nullable="false" />
<ReturnType Type="mscrm.CalculateTotalTimeIncidentResponse" Nullable="false" />
</Function>

This bound function is equivalent to the CalculateTotalTimeIncidentRequest used by the Organization service. In
the Web API this function is bound to the incident EntityType / that represents the
CalculateTotalTimeIncidentRequest.IncidentId property. Instead of returning a
CalculateTotalTimeIncidentResponse, this function returns a CalculateTotalTimeIncidentResponse ComplexType /.
When a function returns a complex type, its definition appears directly above the definition of the function in the
CSDL.
To invoke a bound function, append the full name of the function to the URL and include any named parameters
within the parentheses following the function name. The full function name includes the namespace
Microsoft.Dynamics.CRM . Functions that aren’t bound must not use the full name.
IMPORTANT
A bound function must be invoked using a URI to set the first parameter value. You can’t set it as a named parameter
value.

The following example shows an example using the CalculateTotalTimeIncident Function /, which is bound to the
incident entity.

Request

GET [Organization URI]/api/data/v9.0/incidents(90427858-7a77-e511-80d4-


00155d2a68d1)/Microsoft.Dynamics.CRM.CalculateTotalTimeIncident() HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization
URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.CalculateTotalTimeIncidentResponse","TotalTime":30
}

Unbound functions
The WhoAmI Function / isn’t bound to an entity. It is defined in the CSDL without an IsBound attribute.

<ComplexType Name="WhoAmIResponse">
<Property Name="BusinessUnitId" Type="Edm.Guid" Nullable="false" />
<Property Name="UserId" Type="Edm.Guid" Nullable="false" />
<Property Name="OrganizationId" Type="Edm.Guid" Nullable="false" />
</ComplexType>
<Function Name="WhoAmI">
<ReturnType Type="mscrm.WhoAmIResponse" Nullable="false" />
</Function>

This function corresponds to the WhoAmIRequest and returns a WhoAmIResponse ComplexType / that
corresponds to the WhoAmIResponse used by the Organization service. This function doesn’t have any
parameters.
When invoking an unbound function, use just the function name as shown in the following example.
Request

GET [Organization URI]/api/data/v9.0/WhoAmI() HTTP/1.1


Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.WhoAmIResponse",
"BusinessUnitId": "ded5a64f-f06d-e511-80d0-00155db07cb1",
"UserId": "d96e9f55-f06d-e511-80d0-00155db07cb1",
"OrganizationId": "4faf1f34-f06d-e511-80d0-00155db07cb1"
}

Compose a query with functions


There are two ways that functions can be used to control data returned with queries. Certain functions allow for
control over the columns or conditions that they return and you use query functions to evaluate conditions in a
query.

Composable functions
Some functions listed in Web API Function Reference will return a collection of entities. A subset of these
functions are composable, which means that you can include an additional $select or $filter system query
option to control which columns are returned in the results. These functions have an IsComposable attribute in
the CSDL. Each of these functions has a companion message in the organization service that accept either a
ColumnSet or QueryBase type parameter. The OData system query options provide the same functionality so
these functions do not have the same parameters as their companion messages in the organization service. The
following table shows a list of those composable functions in this release.

F UN C T IO N S F UN C T IO N S ( C O N T 'D) F UN C T IO N S ( C O N T 'D)

GetDefaultPriceLevel Function / RetrieveAllChildUsersSystemUser RetrieveBusinessHierarchyBusinessUnit


Function / Function /

RetrieveByGroupResource Function / RetrieveByResourceResourceGroup RetrieveMembersBulkOperation


Function / Function /

RetrieveParentGroupsResourceGroup RetrieveSubGroupsResourceGroup RetrieveUnpublishedMultiple Function


Function / Function / /

SearchByBodyKbArticle Function / SearchByKeywordsKbArticle Function / SearchByTitleKbArticle Function /

Query functions
Functions listed in Web API Query Function Reference are intended to be used to compose a query. These
functions can be used in a manner similar to the Built-in query functions, but there are some important
differences.
You must use the full name of the function and include the names of the parameters. The following example
shows how to use the LastXHours Function / to return all account entities modified in the past 12 hours.

GET [Organization URI]/api/data/v9.0/accounts?


$select=name,accountnumber&$filter=Microsoft.Dynamics.CRM.LastXHours(PropertyName=@p1,PropertyValue=@p2)&@p1
='modifiedon'&@p2=12

Limitations of query functions


One of the limitations of query functions is that you cannot use the not operator to negate query functions.
For example, the following query using EqualUserId Function / will fail with the error:
Not operator along with the Custom Named Condition operators is not allowed .

GET [Organization URI]/api/data/v9.1/systemusers?$select=fullname,systemuserid&$filter=not


Microsoft.Dynamics.CRM.EqualUserId(PropertyName=@p1)&@p1='systemuserid'

Several query functions have a companion negated query function. For example, you can use the
NotEqualUserId Function /. The following query will return the expected results:

GET [Organization URI]/api/data/v9.1/systemusers?


$select=fullname,systemuserid&$filter=Microsoft.Dynamics.CRM.NotEqualUserId(PropertyName=@p1)&@p1='systemuse
rid'

Other query functions can be negated in different ways. For example, rather than trying to negate the Last7Days
Function / like this (which will fail with the same error as mentioned above):

GET [Organization URI]/api/data/v9.1/accounts?$select=name&$filter=not


Microsoft.Dynamics.CRM.Last7Days(PropertyName=@p1)&@p1='createdon'

Use the OlderThanXDays Function / like this:

GET [Organization URI]/api/data/v9.1/accounts?


$select=name&$filter=Microsoft.Dynamics.CRM.OlderThanXDays(PropertyName=@p1,PropertyValue=@p2)&@p1='createdo
n'&@p2=7

See also
Web API Functions and Actions Sample (C#)
Web API Functions and Actions Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Use Web API actions
5/21/2021 • 7 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Actions and functions represent re-usable operations you can perform using the Web API. Use a POST request
with actions listed in Web API Action Reference to perform operations that have side effects. You can also define
custom actions and they’ll be available for you to use.

Unbound actions
Actions are defined in CSDL metadata document. As an example, the following is the definition of the
WinOpportunity Action represented in the metadata document.

<Action Name="WinOpportunity">
<Parameter Name="OpportunityClose" Type="mscrm.opportunityclose" Nullable="false" />
<Parameter Name="Status" Type="Edm.Int32" Nullable="false" />
</Action>

The WinOpportunity Action corresponds to the WinOpportunityRequest using the organization service. Use this
action to set the state of an opportunity to Won and create an opportunityclose EntityType to record the event.
This action doesn’t include a return value. If it succeeds, the operation is complete.
The OpportunityClose parameter requires a JSON representation of the opportunityclose entity to create in the
operation. This entity must be related to the opportunity issuing the opportunityid single-valued navigation
property. In the JSON this is set using the @odata.bind annotation as explained in Associate tables on create.
The Status parameter must be set to the status to for the opportunity when it is closed. You can find the default
value for this in the opportunity EntityType statuscode property. The Won option has a value of 3. You may ask
yourself, why is it necessary to set this value when there is only one status reason option that represents Won ?
The reason is because you may define custom status options to represent a win, such as Big Win or Small
Win , so the value could potentially be different from 3 in that situation.
The following example is the HTTP request and response to call the WinOpportunity action for an opportunity
with an opportunityid value of b3828ac8-917a-e511-80d2-00155d2a68d2 .
Request
POST [Organization URI]/api/data/v9.0/WinOpportunity HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"Status": 3,
"OpportunityClose": {
"subject": "Won Opportunity",
"[email protected]": "[Organization URI]/api/data/v9.0/opportunities(b3828ac8-917a-e511-80d2-
00155d2a68d2)"
}
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Bound actions
There are two ways that an action can be bound. The most common way is for the action to be bound by an
entity. Less frequently, it can also be bound to an entity collection.
In the CSDL metadata document, when an Action element represents a bound action, it has an IsBound
attribute with the value true . The first Parameter element defined within the action represents the entity that
the operation is bound to. When the Type attribute of the parameter is a collection, the operation is bound to a
collection of entities.
When invoking a bound function, you must include the full name of the function including the
Microsoft.Dynamics.CRM namespace. If you do not include the full name, you will get the following error:
Status Code:400 Request message has unresolved parameters .

Actions bound to a table


As an example of an action bound to an entity, the following is the definition of the AddToQueue Action
represented in the CSDL:

<ComplexType Name="AddToQueueResponse">
<Property Name="QueueItemId" Type="Edm.Guid" Nullable="false" />
</ComplexType>
<Action Name="AddToQueue" IsBound="true">
<Parameter Name="entity" Type="mscrm.queue" Nullable="false" />
<Parameter Name="Target" Type="mscrm.crmbaseentity" Nullable="false" />
<Parameter Name="SourceQueue" Type="mscrm.queue" />
<Parameter Name="QueueItemProperties" Type="mscrm.queueitem" />
<ReturnType Type="mscrm.AddToQueueResponse" Nullable="false" />
</Action>

This entity bound action is equivalent to the AddToQueueRequest used by the organization service. In the Web
API this action is bound to the queue EntityType that represents the AddToQueueRequest.DestinationQueueId
property. This action accepts several additional parameters and returns a AddToQueueResponse ComplexType
corresponding to the AddToQueueResponse returned by the organization service. When an action returns a
complex type, the definition of the complex type will appear directly above the action in the CSDL.
An action bound to an entity must be invoked using a URI to set the first parameter value. You cannot set it as a
named parameter value.
The following example shows using the AddToQueue Action to add a letter to a queue. Because the type of the
Target parameter type is not specific ( mscrm.crmbaseentity ), you must explicitly declare type of the object
using the @odata.type property value of the full name of the entity, including the Microsoft.Dynamics.CRM
namespace. In this case, Microsoft.Dynamics.CRM.letter . More information: Specify entity parameter type
Request

POST [Organization URI]/api/data/v9.0/queues(56ae8258-4878-e511-80d4-


00155d2a68d1)/Microsoft.Dynamics.CRM.AddToQueue HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"Target": {
"activityid": "59ae8258-4878-e511-80d4-00155d2a68d1",
"@odata.type": "Microsoft.Dynamics.CRM.letter"
}
}

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.AddToQueueResponse",
"QueueItemId": "5aae8258-4878-e511-80d4-00155d2a68d1"
}

Actions bound to a table collection


It is less common to find actions bound to an entity collection. The following are some you may find:

CreateException Action/ DeliverIncomingEmail Action/ ExportTranslation Action/

FulfillSalesOrder Action/ ValidateSavedQuery Action

As an example of an action bound to an entity collection, the following is the definition of the ExportTranslation
Action represented in the CSDL:

<ComplexType Name="ExportTranslationResponse">
<Property Name="ExportTranslationFile" Type="Edm.Binary" />
</ComplexType>
<Action Name="ExportTranslation" IsBound="true">
<Parameter Name="entityset" Type="Collection(mscrm.solution)" Nullable="false" />
<Parameter Name="SolutionName" Type="Edm.String" Nullable="false" Unicode="false" />
<ReturnType Type="mscrm.ExportTranslationResponse" Nullable="false" />
</Action>

This entity collection bound action is equivalent to the ExportTranslationRequest used by the organization
service. In the Web API this action is bound to the solution EntityType. But rather than passing a value to the
request, the entity collection binding simply applies the constraint that the URI of the request must include the
path to the specified entity set.
The following example shows using the ExportTranslation Action which exports a binary file containing data
about localizable string values which can be updated to modify or add localizable values. Note how the entity
collection bound action is preceded by the entity set name for the solution entity: solutions .
Request

POST [Organization URI]/api/data/v9.1/solutions/Microsoft.Dynamics.CRM.ExportTranslation HTTP/1.1


Accept: application/json
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"SolutionName":"MySolution"
}

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.1/$metadata#Microsoft.Dynamics.CRM.ExportTranslationResponse",
"ExportTranslationFile": "[Binary data Removed for brevity]"
}

Use a custom action


Regardless of whether the operations included in your custom action have side effects, they can potentially
modify data and therefore are considered actions rather than functions. There is no way to create a custom
function.
Custom action example: Add a note to a contact
Let’s say that you want to create a custom action that will add a new note to a specific contact. You can create a
custom action bound to the contact entity with the following properties.

UI L A B EL VA L UE

Process Name AddNoteToContact

Unique Name new_AddNoteToContact

Entity Contact

Categor y Action

Process Arguments
NAME TYPE REQ UIRED DIREC T IO N

NoteTitle String Required Input

NoteText String Required Input

NoteReference EntityReference Required Output

Steps

NAME ST EP T Y P E DESC RIP T IO N

Create the note Create Record Title = {NoteTitle(Arguments)}


Note Body = {NoteText(Arguments)}
Regarding = {Contact{Contact}}

Return a reference to the note Assign Value NoteReference Value = {Note(Create


the note (Note))}

After you publish and activate the custom action, when you download the CSDL you will find this new action
defined.

<Action Name="new_AddNoteToContact" IsBound="true">


<Parameter Name="entity" Type="mscrm.contact" Nullable="false" />
<Parameter Name="NoteTitle" Type="Edm.String" Nullable="false" Unicode="false" />
<Parameter Name="NoteText" Type="Edm.String" Nullable="false" Unicode="false" />
<ReturnType Type="mscrm.annotation" Nullable="false" />
</Action>

The following HTTP request and response shows how to call the custom action and the response it returns if
successful.
Request

POST [Organization URI]/api/data/v9.0/contacts(94d8c461-a27a-e511-80d2-


00155d2a68d2)/Microsoft.Dynamics.CRM.new_AddNoteToContact HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"NoteTitle": "New Note Title",
"NoteText": "This is the text of the note"
}

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#annotations/$entity",
"annotationid": "9ad8c461-a27a-e511-80d2-00155d2a68d2"
}

Specify table type parameter


When an action requires an entity as a parameter and the type of entity is ambiguous, you must use the
@odata.type property to specify the type of entity. The value of this property is the fully qualified name of the
entity, which follows this pattern: Microsoft.Dynamics.CRM. +<entity logical name>.
As shown in the Bound actions section above, the Target parameter to the AddToQueue Action is an activity.
But since all activities inherit from the activitypointer EntityType, you must include the following property in the
entity JSON to specify the type of entity is a letter: "@odata.type": "Microsoft.Dynamics.CRM.letter" .
Two other examples are AddMembersTeam Action and RemoveMembersTeam Action because the Members
parameter is a collection of systemuser EntityType which inherits it's ownerid primary key from the principal
EntityType. If you pass the following JSON to represent a single systemuser in the collection, it is clear that the
entity is a systemuser and not a team EntityType, which also inherits from the principal entitytype.

{
"Members": [{
"@odata.type": "Microsoft.Dynamics.CRM.systemuser",
"ownerid": "5dbf5efc-4507-e611-80de-5065f38a7b01"
}]
}

If you do not specify the type of entity in this situation, you can get the following error:
"EdmEntityObject passed should have the key property value set." .

See also
Web API Functions and Actions Sample (C#)
Web API Functions and Actions Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Execute batch operations using the Web API
Impersonate another user using the Web API
Perform conditional operations using the Web API
Execute batch operations using the Web API
9/26/2021 • 8 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

You can group multiple operations into a single HTTP request using a batch operation. These operations will be
performed sequentially in the order they are specified.

When to use batch requests


The value that batch requests provide is that they can include change sets, which provide a way to bundle a
number of operations that either succeed or fail as a group. Compared to other operations that can be
performed using the web API, they are more difficult to compose without some object model that includes
serialization of objects or a deeper understanding of the HTTP protocol because the request body is essentially a
text document that must match very specific requirements.
Remember that associated entities can be created in a single operation more easily than using a batch request.
Batch requests are best used when performing operations on entities that aren't associated with each other
when all the operations must be performed in a single transactional operation.
Also, the responses returned are essentially text documents rather than objects that can easily be parsed into
JSON. You'll need to parse the text in the response or locate a helper library to access the data in the response.

NOTE
Batch requests can contain up to 1000 individual requests and cannot contain other batch requests.
URLs for GET requests sent with a batch are limited to 32768 characters.

Batch requests
Use a POST request to submit a batch operation that contains multiple requests. A batch request can include
GET requests and change sets. To use the transactional capabilities of batch requests, only operations that will
change data can be included within a change set. GET requests must not be included in the change set.
The POST request containing the batch must have a Content-Type header with a value set to multipart/mixed
with a boundary set to include the identifier of the batch using this pattern:

--batch_<unique identifier>

The unique identifier doesn't need to be a GUID, but should be unique. Each item within the batch must be
preceded by the batch identifier with a Content-Type and Content-Transfer-Encoding header like the following:
--batch_WKQS9Yui9r
Content-Type: application/http
Content-Transfer-Encoding:binary

The end of the batch must contain a termination indicator like the following:

--batch_WKQS9Yui9r--

Change sets
When multiple operations are contained in a change set, all the operations are considered atomic, which means
that if any one of the operations fail, any completed operations will be rolled back. Like a batch request, change
sets must have a Content-Type header with value set to multipart/mixed with a boundary set to include the
identifier of the change set using this pattern:

--changeset_<unique identifier>

The unique identifier doesn't need to be a GUID, but should be unique. Each item within the change set must be
preceded by the change set identifier with a Content-Type and Content-Transfer-Encoding header like the
following:

--changeset_BBB456
Content-Type: application/http
Content-Transfer-Encoding:binary

Change sets can also include a Content-ID header with a unique value. This value, when prefixed with $ ,
represents a variable that contains the Uri for any entity created in that operation. For example, when you set the
value of 1, you can refer to that entity using $1 later in your change set.
The end of the change set must contain a termination indicator like the following:

--changeset_BBB456--

Handling errors
When an error occurs for a request within a batch, the error for that request will be returned for the batch
request and additional requests will not be processed.
You can use the Prefer: odata.continue-on-error request header to specify that additional requests be
processed when errors occur. The batch request will return 200 OK and individual response errors will be
returned in the batch response body.

Example
The following example includes a batch with a unique identifier of AAA123 and a change set with a unique
identifier of BBB456 .
Within the change set, two tasks are created using POST and associated with an existing account with
accountid = 00000000-0000-0000-000000000001 .
Finally, a GET request is included outside the change set to return all six tasks associated with the account,
including the two that were created in the batch request.
Request

POST[Organization URI]/api/data/v9.1/$batch HTTP/1.1


Content-Type: multipart/mixed;boundary=batch_AAA123
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

--batch_AAA123
Content-Type: multipart/mixed;boundary=changeset_BBB456

--changeset_BBB456
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 1

POST[Organization URI]/api/data/v9.1/tasks HTTP/1.1


Content-Type: application/json;type=entry

{"subject":"Task 1 in batch","[email protected]":"[Organization
URI]/api/data/v9.1/accounts(00000000-0000-0000-000000000001)"}
--changeset_BBB456
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 2

POST[Organization URI]/api/data/v9.1/tasks HTTP/1.1


Content-Type: application/json;type=entry

{"subject":"Task 2 in batch","[email protected]":"[Organization
URI]/api/data/v9.1/accounts(00000000-0000-0000-000000000001)"}
--changeset_BBB456--

--batch_AAA123
Content-Type: application/http
Content-Transfer-Encoding:binary

GET[Organization URI]/api/data/v9.1/accounts(00000000-0000-0000-000000000001)/Account_Tasks?$select=subject
HTTP/1.1
Accept: application/json

--batch_AAA123--

Response
--batchresponse_c1bd45c1-dd81-470d-b897-e965846aad2f
Content-Type: multipart/mixed; boundary=changesetresponse_ff83b4f1-ab48-430c-b81c-926a2c596abc

--changesetresponse_ff83b4f1-ab48-430c-b81c-926a2c596abc
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content


OData-Version: 4.0
Location:[Organization URI]/api/data/v9.1/tasks(a59c24f3-fafc-e411-80dd-00155d2a68cb)
OData-EntityId:[Organization URI]/api/data/v9.1/tasks(a59c24f3-fafc-e411-80dd-00155d2a68cb)

--changesetresponse_ff83b4f1-ab48-430c-b81c-926a2c596abc
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content


OData-Version: 4.0
Location:[Organization URI]/api/data/v9.1/tasks(a69c24f3-fafc-e411-80dd-00155d2a68cb)
OData-EntityId:[Organization URI]/api/data/v9.1/tasks(a69c24f3-fafc-e411-80dd-00155d2a68cb)

--changesetresponse_ff83b4f1-ab48-430c-b81c-926a2c596abc--
--batchresponse_c1bd45c1-dd81-470d-b897-e965846aad2f
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.1/$metadata#tasks(subject)","value":[
{
"@odata.etag":"W/\"474122\"","subject":"Task Created with Test Account","activityid":"919c24f3-fafc-
e411-80dd-00155d2a68cb"
},{
"@odata.etag":"W/\"474125\"","subject":"Task 1","activityid":"a29c24f3-fafc-e411-80dd-00155d2a68cb"
},{
"@odata.etag":"W/\"474128\"","subject":"Task 2","activityid":"a39c24f3-fafc-e411-80dd-00155d2a68cb"
},{
"@odata.etag":"W/\"474131\"","subject":"Task 3","activityid":"a49c24f3-fafc-e411-80dd-00155d2a68cb"
},{
"@odata.etag":"W/\"474134\"","subject":"Task 1 in batch","activityid":"a59c24f3-fafc-e411-80dd-
00155d2a68cb"
},{
"@odata.etag":"W/\"474137\"","subject":"Task 2 in batch","activityid":"a69c24f3-fafc-e411-80dd-
00155d2a68cb"
}
]
}
--batchresponse_c1bd45c1-dd81-470d-b897-e965846aad2f--

Include odata.include-annotations preference header with the GET requests and set its value to "*" to specify
that all annotations related to the properties be returned.
--batch_AAA123
Content-Type: application/http
Content-Transfer-Encoding:binary

GET[Organization URI]/api/data/v9.1/accounts(00000000-0000-0000-000000000001)?
$select=name,telephone1,emailaddress1,shippingmethodcode,customersizecode,accountratingcode,followemail,dono
temail,donotphone,statuscode HTTP/1.1
Accept: application/json
Prefer: odata.include-annotations="*"

--batch_AAA123--

For more information about preference headers, see Header Prefer.

Reference URIs in an operation


You can use $parameter such as $1 , $2 , etc to reference URIs returned for new entities created earlier in the
same changeset in a batch request. For more information see the OData v4.0 specification: 11.7.3.1 Referencing
Requests in a Change Set.
This section shows various examples on how $parameter can be used in the request body of a batch operation
to reference URIs.
Reference URIs in request body
The below example shows how two URI references can be used in a single operation.
Request
POST [Organization URI]/api/data/v9.1/$batch HTTP/1.1
Content-Type: multipart/mixed;boundary=batch_AAA123
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

--batch_AAA123
Content-Type: multipart/mixed; boundary=changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST [Organization URI]/api/data/v9.1/leads HTTP/1.1


Content-Type: application/json

{
"firstname":"aaa",
"lastname":"bbb"
}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

POST [Organization URI]/api/data/v9.1/contacts HTTP/1.1


Content-Type: application/json

{"@odata.type":"Microsoft.Dynamics.CRM.contact","firstname":"Oncall Contact-1111"}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

POST [Organization URI]/api/data/v9.1/accounts HTTP/1.1


Content-Type: application/json

{
"name":"IcM Account",
"[email protected]":"$1",
"[email protected]":"$2"
}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab--
--batch_AAA123--

Response
200 OK

--batchresponse_3cace264-86ea-40fe-83d3-954b336c0f4a
Content-Type: multipart/mixed; boundary=changesetresponse_1a5db8a1-ec98-42c4-81f6-6bc6adcfa4bc

--changesetresponse_1a5db8a1-ec98-42c4-81f6-6bc6adcfa4bc
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content


OData-Version: 4.0
Location: [Organization URI]/api/data/v9.1/leads(425195a4-7a75-e911-a97a-000d3a34a1bd)
OData-EntityId: [Organization URI]/api/data/v9.1/leads(425195a4-7a75-e911-a97a-000d3a34a1bd)

--changesetresponse_1a5db8a1-ec98-42c4-81f6-6bc6adcfa4bc
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content


OData-Version: 4.0
Location: [Organization URI]/api/data/v9.1/contacts(495195a4-7a75-e911-a97a-000d3a34a1bd)
OData-EntityId: [Organization URI]/api/data/v9.1/contacts(495195a4-7a75-e911-a97a-000d3a34a1bd)

--changesetresponse_1a5db8a1-ec98-42c4-81f6-6bc6adcfa4bc
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

HTTP/1.1 204 No Content


OData-Version: 4.0
Location: [Organization URI]/api/data/v9.1/accounts(4f5195a4-7a75-e911-a97a-000d3a34a1bd)
OData-EntityId: [Organization URI]/api/data/v9.1/accounts(4f5195a4-7a75-e911-a97a-000d3a34a1bd)

--changesetresponse_1a5db8a1-ec98-42c4-81f6-6bc6adcfa4bc--
--batchresponse_3cace264-86ea-40fe-83d3-954b336c0f4a--

Reference URI in request URL


The example given below shows how you can reference a URI using $1 in the URL of a subsequent request.
Request
POST [Organization URI]/api/data/v9.1/$batch HTTP/1.1
Content-Type: multipart/mixed;boundary=batch_AAA123
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

--batch_AAA123
Content-Type: multipart/mixed; boundary=changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST [Organization URI]/api/data/v9.1/contacts HTTP/1.1


Content-Type: application/json

{
"@odata.type":"Microsoft.Dynamics.CRM.contact",
"firstname":"Contact",
"lastname":"AAAAAA"
}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Transfer-Encoding: binary
Content-Type: application/http
Content-ID: 2

PUT $1/lastname HTTP/1.1


Content-Type: application/json

{
"value":"BBBBB"
}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab--
--batch_AAA123--

Response
200 OK

--batchresponse_2cb48f48-39a8-41ea-aa52-132fa8ab3c2d
Content-Type: multipart/mixed; boundary=changesetresponse_d7528170-3ef3-41bd-be8e-eac971a8d9d4

--changesetresponse_d7528170-3ef3-41bd-be8e-eac971a8d9d4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content


OData-Version: 4.0
Location:[Organization URI]/api/data/v9.1/contacts(f8ea5d2c-8c75-e911-a97a-000d3a34a1bd)
OData-EntityId:[Organization URI]/api/data/v9.1/contacts(f8ea5d2c-8c75-e911-a97a-000d3a34a1bd)

--changesetresponse_d7528170-3ef3-41bd-be8e-eac971a8d9d4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content


OData-Version: 4.0

--changesetresponse_d7528170-3ef3-41bd-be8e-eac971a8d9d4--
--batchresponse_2cb48f48-39a8-41ea-aa52-132fa8ab3c2d--

Reference URIs in URL and request body using @odata.id


The example given below shows how to link a Contact entity record to an Account entity record. The URI of
Account entity record is referenced as $1 and URI of Contact entity record is referenced as $2 .
Request
POST [Organization URI]/api/data/v9.1/$batch HTTP/1.1
Content-Type:multipart/mixed;boundary=batch_AAA123
Accept:application/json
OData-MaxVersion:4.0
OData-Version:4.0

--batch_AAA123
Content-Type: multipart/mixed; boundary=changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type:application/http
Content-Transfer-Encoding:binary
Content-ID:1

POST [Organization URI]/api/data/v9.1/accounts HTTP/1.1


Content-Type: application/json

{"@odata.type":"Microsoft.Dynamics.CRM.account","name":"IcM Account"}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type:application/http
Content-Transfer-Encoding:binary
Content-ID:2

POST [Organization URI]/api/data/v9.1/contacts HTTP/1.1


Content-Type:application/json

{"@odata.type":"Microsoft.Dynamics.CRM.contact","firstname":"Oncall Contact"}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type:application/http
Content-Transfer-Encoding:binary
Content-ID:3

PUT $1/primarycontactid/$ref HTTP/1.1


Content-Type:application/json

{"@odata.id":"$2"}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab--
--batch_AAA123--

Response
200 OK

--batchresponse_0740a25c-d8e1-41a5-9202-1b50a297864c
Content-Type: multipart/mixed; boundary=changesetresponse_19ca0da8-d8bb-4273-a3f7-fe0d0fadfe5f

--changesetresponse_19ca0da8-d8bb-4273-a3f7-fe0d0fadfe5f
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content


OData-Version: 4.0
Location:[Organization URI]/api/data/v9.1/accounts(3dcf8c02-8c75-e911-a97a-000d3a34a1bd)
OData-EntityId:[Organization URI]/api/data/v9.1/accounts(3dcf8c02-8c75-e911-a97a-000d3a34a1bd)

--changesetresponse_19ca0da8-d8bb-4273-a3f7-fe0d0fadfe5f
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content


OData-Version: 4.0
Location:[Organization URI]/api/data/v9.1/contacts(43cf8c02-8c75-e911-a97a-000d3a34a1bd)
OData-EntityId:[Organization URI]/api/data/v9.1/contacts(43cf8c02-8c75-e911-a97a-000d3a34a1bd)

--changesetresponse_19ca0da8-d8bb-4273-a3f7-fe0d0fadfe5f
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

HTTP/1.1 204 No Content


OData-Version: 4.0

--changesetresponse_19ca0da8-d8bb-4273-a3f7-fe0d0fadfe5f--
--batchresponse_0740a25c-d8e1-41a5-9202-1b50a297864c--

Reference URIs in URL and navigation properties


The example given below shows how to use the Organization URI of a Contact record and link it to an Account
record using the primarycontactid single-valued navigation property. The URI of the Account entity record is
referenced as $1 and the URI of Contact entity record is referenced as $2 in the PATCH request.
Request
POST [Organization URI]/api/data/v9.1/$batch HTTP/1.1
Content-Type:multipart/mixed;boundary=batch_AAA123
Accept:application/json
OData-MaxVersion:4.0
OData-Version:4.0

--batch_AAA123
Content-Type: multipart/mixed; boundary=changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST [Organization URI]/api/data/v9.1/accounts HTTP/1.1


Content-Type: application/json

{"@odata.type":"Microsoft.Dynamics.CRM.account","name":"IcM Account"}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

POST [Organization URI]/api/data/v9.1/contacts HTTP/1.1


Content-Type: application/json

{
"@odata.type":"Microsoft.Dynamics.CRM.contact",
"firstname":"Oncall Contact"
}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

PATCH $1 HTTP/1.1
Content-Type: application/json

{
"[email protected]":"$2"
}

--changeset_dd81ccab-11ce-4d57-b91d-12c4e25c3cab--
--batch_AAA123--

Response
200 OK

--batchresponse_9595d3ae-48f6-414f-a3aa-a3a33559859e
Content-Type: multipart/mixed; boundary=changesetresponse_0c1567a5-ad0d-48fa-b81d-e6db05cad01c

--changesetresponse_0c1567a5-ad0d-48fa-b81d-e6db05cad01c
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content


OData-Version: 4.0
Location: [Organization URI]/api/data/v9.1/accounts(6cd81853-7b75-e911-a97a-000d3a34a1bd)
OData-EntityId: [Organization URI]/api/data/v9.1/accounts(6cd81853-7b75-e911-a97a-000d3a34a1bd)

--changesetresponse_0c1567a5-ad0d-48fa-b81d-e6db05cad01c
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content


OData-Version: 4.0
Location: [Organization URI]/api/data/v9.1/contacts(6ed81853-7b75-e911-a97a-000d3a34a1bd)
OData-EntityId: [Organization URI]/api/data/v9.1/contacts(6ed81853-7b75-e911-a97a-000d3a34a1bd)

--changesetresponse_0c1567a5-ad0d-48fa-b81d-e6db05cad01c
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

HTTP/1.1 204 No Content


OData-Version: 4.0
Location: [Organization URI]/api/data/v9.1/accounts(6cd81853-7b75-e911-a97a-000d3a34a1bd)
OData-EntityId: [Organization URI]/api/data/v9.1/accounts(6cd81853-7b75-e911-a97a-000d3a34a1bd)

--changesetresponse_0c1567a5-ad0d-48fa-b81d-e6db05cad01c--
--batchresponse_9595d3ae-48f6-414f-a3aa-a3a33559859e--
NOTE
Referencing a Content-ID before it has been declared in the request body will return the error HTTP 400 Bad request.
The example given below shows a request body that can cause this error.
Request body

--batch_AAA123
Content-Type: multipart/mixed; boundary=changeset_BBB456

--changeset_BBB456
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 2

POST [Organization URI]/api/data/v9.1/phonecalls HTTP/1.1


Content-Type: application/json;type=entry

{
"phonenumber":"911",
"[email protected]" : "$1"
}

--changeset_BBB456
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 1

POST [Organization URI]/api/data/v9.1/accounts HTTP/1.1


Content-Type: application/json;type=entry

{
"name":"QQQQ",
"revenue": 1.50
}

--changeset_BBB456--
--batch_AAA123--

Response

HTTP 400 Bad Request


Content-ID Reference: '$1' does not exist in the batch context.

See also
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Impersonate another user using the Web API
Perform conditional operations using the Web API
Impersonate another user using the Web API
5/21/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

There are times when your code will need to perform operations on behalf of another user. If the system account
running your code has the necessary privileges, you can perform operations on behalf of other users.

Requirements for impersonation


Impersonation is used to execute business logic (code) on behalf of another Microsoft Dataverse user to provide
a desired feature or service using the appropriate role and object-based security of that impersonated user. This
is necessary because the Dataverse Web services can be called by various clients and services on behalf of a
Dataverse user, for example, in a workflow or custom ISV solution. Impersonation involves two different user
accounts: one user account (A) is used when executing code to perform some task on behalf of another user (B).
User account (A) needs the prvActOnBehalfOfAnotherUser privilege, which is included in the Delegate security
role. The actual set of privileges that is used to modify data is the intersection of the privileges that the Delegate
role user possesses with that of the user who is being impersonated. In other words, user (A) is allowed to do
something if and only if user (A) and the impersonated user (B) have the privilege necessary for the action.

How to impersonate a user


There are two ways you can impersonate a user, both of which are made possible by passing in a header with
the corresponding user id.
1. Preferred: Impersonate a user based on their Azure Active Directory (AAD) object id by passing that value
along with the header CallerObjectId .
2. Legacy: To impersonate a user based on their systemuserid you can leverage MSCRMCallerID with the
corresponding guid value.
In this example, a new account entity is created on behalf of the user with an Azure Active Directory object id
e39c5d16-675b-48d1-8e67-667427e9c084 .

Request

POST [Organization URI]/api/data/v9.0/accounts HTTP/1.1


CallerObjectId: e39c5d16-675b-48d1-8e67-667427e9c084
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{"name":"Sample Account created using impersonation"}

Response
HTTP/1.1 204 No Content
OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-000000000003)

Determine the actual user


When an operation such as creating an entity is performed using impersonation, the user who actually
performed the operation can be found by querying the record including the createdonbehalfby single-valued
navigation property. A corresponding modifiedonbehalfby single-valued navigation property is available for
operations that update the entity.
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-000000000003)?


$select=name&$expand=createdby($select=fullname),createdonbehalfby($select=fullname),owninguser($select=full
name) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
ETag: W/"506868"

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#accounts(name,createdby(fullname,azureactivedirectoryobjectid),createdonbehalfb
y(fullname,azureactivedirectoryobjectid),owninguser(fullname,azureactivedirectoryobjectid))/$entity",
"@odata.etag": "W/\"2751197\"",
"name": "Sample Account created using impersonation",
"accountid": "00000000-0000-0000-000000000003",
"createdby": {
"@odata.etag": "W/\"2632435\"",
"fullname": "Impersonated User",
"azureactivedirectoryobjectid": "e39c5d16-675b-48d1-8e67-667427e9c084",
"systemuserid": "75df116d-d9da-e711-a94b-000d3a34ed47",
"ownerid": "75df116d-d9da-e711-a94b-000d3a34ed47"
},
"createdonbehalfby": {
"@odata.etag": "W/\"2632445\"",
"fullname": "Actual User",
"azureactivedirectoryobjectid": "3d8bed3e-79a3-47c8-80cf-269869b2e9f0",
"systemuserid": "278742b0-1e61-4fb5-84ef-c7de308c19e2",
"ownerid": "278742b0-1e61-4fb5-84ef-c7de308c19e2"
},
"owninguser": {
"@odata.etag": "W/\"2632435\"",
"fullname": "Impersonated User",
"azureactivedirectoryobjectid": "e39c5d16-675b-48d1-8e67-667427e9c084",
"systemuserid": "75df116d-d9da-e711-a94b-000d3a34ed47",
"ownerid": "75df116d-d9da-e711-a94b-000d3a34ed47"
}
}

See also
Impersonate another user
Impersonate another user using the Organization service
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Perform conditional operations using the Web API
Perform conditional operations using the Web API
5/21/2021 • 6 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Microsoft Dataverse provides support for a set of conditional operations that rely upon the standard HTTP
resource versioning mechanism known as ETags.

ETags
The HTTP protocol defines an entity tag, or ETag for short, for identifying specific versions of a resource. ETags
are opaque identifiers whose exact values are implementation dependent. ETag values occur in two varieties:
strong and weak validation. Strong validation indicates that a unique resource, identified by a specific URI, will
be identical on the binary level if its corresponding ETag value is unchanged. Weak validation only guarantees
that the resource representation is semantically equivalent for the same ETag value.
Dataverse generates a weakly validating @odata.etag property for every entity instance, and this property is
automatically returned with each retrieved entity record. For more information, see Retrieve a table using the
Web API.

If-Match and If-None-Match headers


Use If-Match and If-None-Match headers with ETag values to check whether the current version of a resource
matches the one last retrieved, matches any previous version or matches no version. These comparisons form
the basis of conditional operation support. Dataverse provides ETags to support conditional retrievals, optimistic
concurrency, and limited upsert operations.

WARNING
Client code should not give any meaning to the specific value of an ETag, nor to any apparent relationship between ETags
beyond equality or inequality. For example, an ETag value for a more recent version of a resource is not guaranteed to be
greater than the ETag value for an earlier version. Also, the algorithm used to generate new ETag values may change
without notice between releases of a service.

Conditional retrievals
Etags enable you to optimize record retrievals whenever you access the same record multiple times. If you have
previously retrieved a record, you can pass the ETag value with the If-None-Match header to request data to be
retrieved only if it has changed since the last time it was retrieved. If the data has changed, the request returns
an HTTP status of 200 (OK) with the latest data in the body of the request. If the data hasn't changed, the HTTP
status code 304 (Not Modified) is returned to indicate that the entity hasn't been modified.
The following example message pair returns data for an account entity with the accountid equal to
00000000-0000-0000-0000-000000000001 when the data hasn't changed since it was last retrieved when the Etag
value was W/"468026"
Request

GET [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001)?


$select=accountcategorycode,accountnumber,creditonhold,createdon,numberofemployees,name,revenue HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
If-None-Match: W/"468026"

Response

HTTP/1.1 304 Not Modified


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

The following sections describe limitations to using conditional retrievals.


Table must have optimistic concurrency enabled
Check if an entity has optimistic concurrency enabled using the Web API request shown below. Entities that have
optimistic concurrency enabled will have EntityMetadata.IsOptimisticConcurrencyEnabled property set to true .

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='<Entity Logical Name>')?


$select=IsOptimisticConcurrencyEnabled

Query must not include $expand


The Etag can only detect if the single record that is being retrieved has changed. When you use $expand in your
query, additional records may be returned and it is not possible to detect whether or not any of those records
have changed. If the query includes $expand it will never return 304 Not Modified .
Query must not include annotations
When the Prefer: odata.include-annotations header is included with a GET request it will never return
304 Not Modified . The values of annotations can refer to values from related records. These records may have
changed and this change could not be detected, so it would be incorrect to indicate that nothing has changed.

Limit upsert operations


An upsert ordinarily operates by creating an entity if it doesn't exist; otherwise, it updates an existing entity.
However, ETags can be used to further constrain upserts to either prevent creates or to prevent updates.

Prevent create in upsert


If you are updating data and there is some possibility that the entity was deleted intentionally, you will not want
to re-create the entity. To prevent this, add an If-Match header to the request with a value of "*".
Request
PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
If-Match: *

{
"name": "Updated Sample Account ",
"creditonhold": true,
"address1_latitude": 47.639583,
"description": "This is the updated description of the sample account",
"revenue": 6000000,
"accountcategorycode": 2
}

Response
If the entity is found, you'll get a normal response with status 204 (No Content). When the entity is not found,
you'll get the following response with status 404 (Not Found).

HTTP/1.1 404 Not Found


OData-Version: 4.0
Content-Type: application/json; odata.metadata=minimal

{
"error": {
"code": "",
"message": "account With Id = 00000000-0000-0000-0000-000000000001 Does Not Exist"
}
}

Prevent update in upsert


If you're inserting data, there is some possibility that a record with the same id value already exists in the
system and you may not want to update it. To prevent this, add an If-None-Match header to the request with a
value of "*".
Request

PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
If-None-Match: *

{
"name": "Updated Sample Account ",
"creditonhold": true,
"address1_latitude": 47.639583,
"description": "This is the updated description of the sample account",
"revenue": 6000000,
"accountcategorycode": 2
}

Response
If the entity isn't found, you will get a normal response with status 204 (No Content). When the entity is found,
you'll get the following response with status 412 (Precondition Failed).
HTTP/1.1 412 Precondition Failed
OData-Version: 4.0
Content-Type: application/json; odata.metadata=minimal

{
"error":{
"code":"",
"message":"A record with matching key values already exists."
}
}

Apply optimistic concurrency


You can use optimistic concurrency to detect whether an entity has been modified since it was last retrieved. If
the entity you intend to update or delete has changed on the server since you retrieved it, you may not want to
complete the update or delete operation. By applying the pattern shown here you can detect this situation,
retrieve the most recent version of the entity, and apply any necessary criteria to re-evaluate whether to try the
operation again.

Apply optimistic concurrency on delete


The following delete request for an account with accountid of 00000000-0000-0000-0000-000000000001 fails
because the ETag value sent with the If-Match header is different from the current value. If the value had
matched, a 204 (No Content) status is expected.
Request

DELETE [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1


If-Match: W/"470867"
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 412 Precondition Failed


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"error":{
"code":"","message":"The version of the existing record doesn't match the RowVersion property provided."
}
}

Apply optimistic concurrency on update


The following update request for an account with accountid of 00000000-0000-0000-0000-000000000001 fails
because the ETag value sent with the If-Match header is different from the current value. If the value had
matched, a 204 (No Content) status is expected.
Request
PATCH [Organization URI]/api/data/v9.0/accounts(00000000-0000-0000-0000-000000000001) HTTP/1.1
If-Match: W/"470867"
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

{"name":"Updated Account Name"}

Response

HTTP/1.1 412 Precondition Failed


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"error":{
"code":"","message":"The version of the existing record doesn't match the RowVersion property provided."
}
}

See also
Web API Conditional Operations Sample (C#)
Web API Conditional Operations Sample (Client-side JavaScript)
Perform operations using the Web API
Compose Http requests and handle errors
Query Data using the Web API
Create a table using the Web API
Retrieve a table using the Web API
Update and delete tables using the Web API
Associate and disassociate tables using the Web API
Use Web API functions
Use Web API actions
Execute batch operations using the Web API
Impersonate another user using the Web API
Detect duplicate data using the Web API
5/21/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Microsoft Dataverse Web API allows you to detect duplicate table rows of an existing row in order to maintain
integrity of data. For detailed information about detecting duplicate data using code, see Detect duplicate data
using code

Detect duplicates during Create operation


Use MSCRM.SuppressDuplicateDetection header during POST request, to detect creation of a duplicate record of
an existing record. The value assigned to MSCRM.SuppressDuplicateDetection header determines whether the
Create or Update operation can be completed:
true – Create or update the record, if a duplicate is found.
false – Do not create or update the record, if a duplicate is found.

NOTE
Passing of the CalculateMatchCodeSynchronously optional parameter is not required. The match codes used to detect
duplicates are calculated synchronously regardless of the value passed in this parameter.

Use preference header MSCRM.SuppressDuplicateDetection and set its value to false in the Web API request.

NOTE
Make sure there are appropriate duplicate detection rules in place. Dataverse includes default duplicate detection rules for
accounts, contacts, and leads, but not for other types of records. If you want the system to detect duplicates for other
record types, you'll need to create a new rule.
- For information on how to create a duplicate detection rule using the UI, see Set up duplicate detection rules to keep
your data clean.
- For information on creating duplicate detection rules using code, see Duplicate rule tables

Example: Detect duplicates during Create operation using the Web API
The following example shows how to detect duplicates during Create and Update operations using
MSCRM.SuppressDuplicateDetection header in Web API request.

Request
POST [Organization URI]/org1/api/data/v9.0/leads HTTP/1.1
If-None-Match: null
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json
Accept: application/json
MSCRM.SuppressDuplicateDetection: false

{
"firstname":"Monte",
"lastname":"Orton",
"emailaddress1":"[email protected]"
}

If a lead record with the same emailaddress1 attribute already exists, the following Response is returned.
Response

HTTP/1.1 500 Internal Server Error


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"error": {
"code": "0x80040333",
"message": "A record was not created or updated because a duplicate of the current record already
exists."
}
}

Assign value true to MSCRM.SuppressDuplicateDetection header to allow creation of a duplicate record.

Detect duplicates during Update operation


Set the value of MSCRM.SuppressDuplicateDetection header to false in your PATCH request to avoid creation of
a duplicate record during Update operation. By default, duplicate detection is suppressed when you are updating
records using the Web API.
Example: Detect duplicates during Update operation using the Web API
The example shown below attempts to update an existing lead entity record which includes the same value of
emailaddress1 attribute as an existing record.

Request

PATCH [Organization URI]/api/data/v9.0/leads(c4567bb6-47a3-e711-811b-e0071b6ac1b1) HTTP/1.1


If-None-Match: null
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json
Accept: application/json
MSCRM.SuppressDuplicateDetection: false
If-Match: *

{
"firstname":"Monte",
"lastname":"Orton",
"emailaddress1":"[email protected]"
}
Response

HTTP/1.1 500 Internal Server Error


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"error": {
"code": "0x80040333",
"message": "A record was not created or updated because a duplicate of the current record already
exists."
}
}

See Also
Detect duplicate data using the Organization service
Access entity data faster using storage partitions
5/3/2021 • 2 minutes to read • Edit Online

An optional partition key can be specified to create a logical partition for non-relational custom entity data
stored in NoSql tables of Azure heterogenous storage (Cosmos DB). Having a partition key improves application
performance for large sets of data (millions of records) by grouping data items into logical sets within a table.
For example, a table containing products can be grouped logically into product categories to improve retrieval
of all items within a product category. The partition key value can be a string or numeric type. Once specified,
the partition key value can't be changed.
When no partition key is specified, the table is the logical boundary and retrieving a single item or a set of
logically related items from a large data set will not be as performant as when using a partition key.

Creating and accessing a partition


A unique new partition key value must be used to create a new logical partition. The same value must be used
to create additional items in the same logical partition and to retrieve, update, or delete items belonging to the
logical partition.
An HTTP command that creates a new customer entity record in a new storage partition named
"CustomerPartition".

POST [Organization URI]/api/data/v9.1/new_msdyn_customers?partitionid="CustomerPartition"


Content-Type: application/json
{
"new_firstname": "Monica",
"new_lastname": "Thompson",
}

HTTP commands that retrieve one and all customer records from the named storage partition.

GET [Organization URI]/api/data/v9.1/new_msdyn_customers(<cID>)?partitionId="CustomerPartition"


GET [Organization URI]/api/data/v9.1/new_msdyn_customers?partitionId="CustomerPartition"

An HTTP command that updates a customer record in the named storage partition.

PATCH [Organization URI]/api/data/v9.1/new_msdyn_customers(<cID>)?partitionid="CustomerPartition"


Content-Type: application/json
{
"new_firstname": "Cora",
"new_lastname": "Thomas",
}

An HTTP command that deletes a customer record in the named storage partition.

DELETE [Organization URI]/api/data/v9.1/new_msdyn_customers(<cID>)?partitionId="CustomerPartition"


IMPORTANT
For a GET or DELETE command, the partition identifier parameter is case sensitive and must be specified with e capital "I"
as in "partitionI d".

Additional information
Here are a few more details about the partition key and partition management.
The key value must be unique in the environment.
A partition is limited to 20 GB of data, and there is no method available to check the partition's current size.
There is no defined limit to the number of partitions you can allocate in an environment.
Partition allocation is automatic. Specifying a unique partition key during a Create operation creates a
partition. When all data has been deleted from the partition, the partition is deleted.
There is no method available to rename a key.
Presently, only the Create, Update, Retrieve, and Delete entity operations support storage partitioning.
See Also
Create an entity record using the Web API
Retrieve an entity record using the Web API
Update and delete entities using the Web API
Partitioning and horizontal scaling in Azure Cosmos DB
Use the Web API with table definitions
4/30/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

You can perform any of the table and column definition (metadata) operations with the Web API that you can
perform using the Organization service. This section provides guidance about how to use the Web API with the
entity types included in Web API Metadata EntityType Reference.
There are four entity set paths exposed to perform operations with definition entities as described in the
following table.

EN T IT Y SET PAT H DESC RIP T IO N

[Organization URI]/api/data/v9.0/EntityDefinitions Contains a collection of EntityMetadata EntityType/.

[Organization URI]/api/data/v9.0/RelationshipDefinitions Contains ManyToManyRelationshipMetadata EntityType/ and


OneToManyRelationshipMetadata EntityType/ as both inherit
from RelationshipMetadataBase EntityType/.

[Organization URI]/api/data/v9.0/GlobalOptionSetDefinitions Contains a collection of globally defined


BooleanOptionSetMetadata EntityType/ and
OptionSetMetadata EntityType/ as both inherit from
OptionSetMetadata EntityType/.

[Organization For internal use only


URI]/api/data/v9.0/ManagedPropertyDefinitions

Each definition entity type uses MetadataId as the unique identifier property, which it inherits from the
MetadataBase EntityType/. While all definition entities have a MetadataId , you can’t query all of them directly.
For example, you can query and perform operations on attributes (table columns) only in the context of the
EntityMetadata entity that contains them.

These definition entities have some substantial differences from the tables that store business and application
data, for example:
The properties for definition entities use many of the complex and enum types defined in Web API
ComplexType Reference and Web API EnumType Reference rather than the primitive data types used for
properties in entities that inherit from crmbaseentity EntityType/.
Definition entities follow a different naming convention and maintain the Pascal Case naming style used
in the assemblies of the Organization service.
Definition entities make more extensive use of inheritance, which requires that you may need to perform
casts to retrieve the data that you want.

In This Section
Query table definitions using the Web API
You can use the Web API to query table or column definitions using a RESTful query style.
Retrieve table definitions by name or MetadataId
Your applications can adapt to configuration changes by querying the table and column definitions. When you
know one of the key properties of a definition item, you can retrieve definitions using the Web API.
Create and update table definitions using the Web API
You can create and update tables and columns using the Web API to achieve the same results you get with the
organization service CreateEntityRequest, UpdateEntityRequest, CreateAttributeRequest, and
UpdateAttributeRequest.
Create and update table relationships using the Web API
You can check whether tables are eligible to participate in a relationship with other tables and then create or
update those relationships using the Web API.
See also
Browse the table definitions for your environment
Use the Microsoft Dataverse Web API
Query table definitions using the Web API
4/30/2021 • 7 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Because Microsoft Dataverse is a metadata-driven application, developers may need to query the system
definitions at run-time to adapt to how an organization has been configured. This capability uses a RESTful
query style.

NOTE
You can also construct a query using an object-based style using the EntityQueryExpression ComplexType/ with the
RetrieveMetadataChanges Function/. This function allows for capturing changes to table definitions between two periods
of time as well as returning a limited set of definitions described by a query you specify.

Querying the EntityMetadata entity type


You’ll use the same techniques described in Query data using the Web API when you query EntityMetadata, with
a few variations. Use the EntityDefinitions entity set path to retrieve information about the EntityMetadata
EntityType/. EntityMetadata entities contain a lot of data so you will want to be careful to only retrieve the data
that you need. The following example shows the data returned for just the DisplayName,
IsKnowledgeManagementEnabled, and EntitySetName properties of the definition for the Account entity. The
MetadataId property value is always returned.

Request

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')?


$select=DisplayName,IsKnowledgeManagementEnabled,EntitySetName HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#EntityDefinitions(DisplayName,IsKnowledgeManagementEnabled,EntitySetName)",
"value": [
{
"DisplayName": {
"LocalizedLabels": [
{
"Label": "Account",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "2a4901bf-2241-db11-898a-0007e9e17ebd",
"HasChanged": null
}
],
"UserLocalizedLabel": {
"Label": "Account",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "2a4901bf-2241-db11-898a-0007e9e17ebd",
"HasChanged": null
}
},
"IsKnowledgeManagementEnabled": false,
"EntitySetName": "accounts",
"MetadataId": "70816501-edb9-4740-a16c-6a5efbc05d84"
}
]
}

You can use any of the EntityMetadata properties with $select system query options and you can use
$filter on any properties which use primitive or enumeration values.

There are no limits on the number of metadata entities that will be returned in a query. There is no paging. All
matching resources will be returned in the first response.

Use enum types in $filter operations


When you need to filter metadata entities based on the value of a property that uses an enumeration, you must
include the namespace of the enumeration before the string value. Enum types are used as property values only
in metadata entities and complex types. For example, if you need to filter entities based on the OwnershipType
property, which uses the OwnershipTypes EnumType, you can use the following $filter to return only those
entities that are UserOwned.

GET [Organization URI]/api/data/v9.0/EntityDefinitions?$select=LogicalName&$filter=OwnershipType eq


Microsoft.Dynamics.CRM.OwnershipTypes'UserOwned'

Use complex types in $filter operations


When you need to filter metadata entities based on the value of a property that uses a complex type, you must
include the path to the underlying primitive type. Complex types are used as property values only in metadata
entities. For example, if you need to filter entities based on the CanCreateAttributes property, which uses the
BooleanManagedProperty ComplexType, you can use the following $filter to return only those entities that
have a Value of true .

GET [Organization URI]/api/data/v9.0/EntityDefinitions?$select=LogicalName&$filter=CanCreateAttributes/Value


eq true

This pattern works with BooleanManagedProperty ComplexType because the primitive value to check is one
level deep. However, this does not work on properties of Label ComplexType.

Querying EntityMetadata attributes


You can query entity attributes in the context of an entity by expanding the Attributes collection-valued
navigation property but this will only include the common properties available in the AttributeMetadata
EntityType which all attributes share. For example the following query will return the LogicalName of the entity
and all the expanded Attributes which have an AttributeType value equal to the AttributeTypeCode EnumType
value of Picklist .

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')?


$select=LogicalName&$expand=Attributes($select=LogicalName;$filter=AttributeType eq
Microsoft.Dynamics.CRM.AttributeTypeCode'Picklist')

But you can’t include the OptionSet or GlobalOptionSet collection-valued navigation properties that
PicklistAttributeMetadata EntityType attributes have within the $select filter of this query.
In order to retrieve the properties of a specific type of attribute you must cast the Attributes collection-valued
navigation property to the type you want. The following query will return only the PicklistAttributeMetadata
EntityType attributes and will include the LogicalName as well as expanding the OptionSet and GlobalOptionSet
collection-valued navigation properties

GET [Organization
URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes/Microsoft.Dynamics.CRM.PicklistAttrib
uteMetadata?$select=LogicalName&$expand=OptionSet,GlobalOptionSet

NOTE
Despite the fact that the OptionSet and GlobalOptionSet collection-valued navigation properties are defined within
EnumAttributeMetadata EntityType, you cannot cast the attributes to this type. This means that if you want to filter on
other types which also inherit these properties (see Entity types that inherit from EnumAttributeMetadata ), you must
perform separate queries to filter for each type.

Another example of this is accessing the Precision property available in MoneyAttributeMetadata EntityType
and DecimalAttributeMetadata EntityType attributes. To access this property you must cast the attributes
collection either as MoneyAttributeMetadata EntityType or DecimalAttributeMetadata EntityType. An example
showing casting to MoneyAttributeMetadata is shown here.

GET [Organization
URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes/Microsoft.Dynamics.CRM.MoneyAttribute
Metadata?$select=LogicalName,Precision

Filtering by required level


The AttributeMetadata EntityType RequiredLevel property uses a special
AttributeRequiredLevelManagedProperty ComplexType where the Value property is a AttributeRequiredLevel
EnumType. In this case you must combine patterns found in Use complex types in $filter operations and Use
enum types in $filter operations to filter by this unique property. The following query will filter those attributes
in the account entity that are ApplicationRequired.

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes?


$select=SchemaName&$filter=RequiredLevel/Value eq
Microsoft.Dynamics.CRM.AttributeRequiredLevel'ApplicationRequired'

Retrieving attributes
When you know the MetadataId for both the EntityMetadata and the AttributeMetadata, you can retrieve an
individual attribute and access the property values using a query like the following. This query retrieves the
LogicalName property of the attribute as well as expanding the OptionSet collection-valued navigation property.
Note that you must cast the attribute as a Microsoft.Dynamics.CRM.PicklistAttributeMetadata to access the
OptionSet collection-valued navigation property.
Request

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes(5967e7cc-afbb-4c10-


bf7e-e7ef430c52be)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet
HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions(70816501-edb9-4740-a16c-
6a5efbc05d84)/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata(LogicalName,OptionSet)/$entity",
"LogicalName": "preferredappointmentdaycode",
"MetadataId": "5967e7cc-afbb-4c10-bf7e-e7ef430c52be",
"[email protected]": "[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions(70816501-edb9-4740-
a16c-6a5efbc05d84)/Attributes(5967e7cc-afbb-4c10-bf7e-
e7ef430c52be)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata/OptionSet/$entity",
"OptionSet": {
"Options": [
{
"Value": 0,
"Label": {
"LocalizedLabels": [
{
"Label": "Sunday",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "21d6a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}
],
"UserLocalizedLabel": {
"Label": "Sunday",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "21d6a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
"HasChanged": null
}
},
"Description": {
"LocalizedLabels": [],
"UserLocalizedLabel": null
},
"Color": null,
"IsManaged": true,
"MetadataId": null,
"HasChanged": null
}
Additional options removed for brevity
],
"Description": {
"LocalizedLabels": [
{
"Label": "Day of the week that the account prefers for scheduling service activities.",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "1b67144d-ece0-4e83-a38b-b4d48e3f35d5",
"HasChanged": null
}
],
"UserLocalizedLabel": {
"Label": "Day of the week that the account prefers for scheduling service activities.",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "1b67144d-ece0-4e83-a38b-b4d48e3f35d5",
"HasChanged": null
}
},
"DisplayName": {
"LocalizedLabels": [
{
"Label": "Preferred Day",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "ebb7e979-f9e3-40cd-a86d-50b479b1c5a4",
"HasChanged": null
}
],
"UserLocalizedLabel": {
"Label": "Preferred Day",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "ebb7e979-f9e3-40cd-a86d-50b479b1c5a4",
"HasChanged": null
}
},
"IsCustomOptionSet": false,
"IsGlobal": false,
"IsManaged": true,
"IsCustomizable": {
"Value": true,
"CanBeChanged": false,
"ManagedPropertyLogicalName": "iscustomizable"
},
"Name": "account_preferredappointmentdaycode",
"OptionSetType": "Picklist",
"IntroducedVersion": null,
"MetadataId": "53f9933c-18a0-40a6-b4a5-b9610a101735",
"HasChanged": null
}
}

If you don’t require any properties of the attribute and only want the values of a collection-valued navigation
property such as OptionsSet, you can include that in the URL and limit the properties with a $select system
query option for a somewhat more efficient query. In the following example only the Options property of the
OptionSet are included.
Request

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes(5967e7cc-afbb-4c10-


bf7e-e7ef430c52be)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata/OptionSet?$select=Options HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#EntityDefinitions('account')/Attributes(5967e7cc-afbb-4c10-bf7e-
e7ef430c52be)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata/OptionSet(Options)/$entity",
"Options": [{
"Value": 0,
"Label": {
"LocalizedLabels": [{
"Label": "Sunday",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "21d6a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}],
"UserLocalizedLabel": {
"Label": "Sunday",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "21d6a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}
},
"Description": {
"LocalizedLabels": [],
"UserLocalizedLabel": null
},
"Color": null,
"IsManaged": true,
"MetadataId": null,
"HasChanged": null
}
Additional options removed for brevity
],
"MetadataId": "53f9933c-18a0-40a6-b4a5-b9610a101735"
}

Querying relationship metadata


You can retrieve relationship metadata in the context of a given entity much in the same way that you can query
attributes. The ManyToManyRelationships , ManyToOneRelationships , and OneToManyRelationships collection-valued
navigation properties can be queried just like the Attributes collection-valued navigation property. More
information: Querying EntityMetadata Attributes
However, entity relationships can also be queried using the RelationshipDefinitions entity set. You can use a
query like the following to get the SchemaName property for every relationship.

GET [Organization URI]/api/data/v9.0/RelationshipDefinitions?$select=SchemaName

The properties available when querying this entity set are limited to those in the RelationshipMetadataBase
EntityType. To access properties from the entity types that inherit from RelationshipMetadataBase you need to
include a cast in the query like the following one to return only OneToManyRelationshipMetadata EntityType.

GET [Organization
URI]/api/data/v9.0/RelationshipDefinitions/Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata?
$select=SchemaName

Because the entities returned are typed as OneToManyRelationshipMetadata , you can filter on the properties such
as ReferencedEntity to construct a query to return only the one-to-many entity relationships for a specific
entity, such as the account entity as shown in the following query:

GET [Organization
URI]/api/data/v9.0/RelationshipDefinitions/Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata?
$select=SchemaName&$filter=ReferencedEntity eq 'account'

That query will return essentially the same results as the following query, which is filtered because it is included
in the EntityMetadataOneToManyRelationships collection-valued navigation property of the account entity. The
difference is that for the previous query you don’t need to know the MetadataId for the account entity.

GET [Organization URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/OneToManyRelationships?


$select=SchemaName

Querying Global OptionSets


You can use the GlobalOptionSetDefinitions entity set path to retrieve information about global option sets, but
this path does not support the use of the $filter system query option. So, unless you know the MetadataId
for a specific global option set, you can only retrieve all of them. You can also access the definition of a global
option set from within the GlobalOptionSet single-valued navigation property for any attribute that uses it. This
is available for all the EnumAttributeMetadata EntityType Derived Types. More information: Retrieving attributes
See also
Use the Web API with Dataverse metadata
Retrieve metadata by name or MetadataId
Metadata entites and attributes using the Web API
Metadata entity relationships using the Web API
Retrieve table definitions by name or MetadataId
4/30/2021 • 3 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Your applications can adapt to configuration changes by querying the table and column definitions (metadata).
When you know one of the key properties of a definition item, you can retrieve definitions using the Web API.

Retrieve definition items by name


All retrievable definition items have a MetadataId primary key that can be used to retrieve individual items. For
those types of definition which have a defined alternate key, you can retrieve them by name.
Retrieving definition items by name is generally easier because you probably already have some reference to
the item name in your code. The following table lists the alternate key properties for retrieving such items by
name.

DEF IN IT IO N IT EM A LT ERN AT E K EY EXA M P L E

Entity LogicalName GET


/api/data/v9.0/EntityDefinitions(LogicalName='account')

Attribute LogicalName GET


/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes(LogicalName=

Relationship SchemaName GET


/api/data/v9.0/RelationshipDefinitions(SchemaName='Account_Tasks')

Global Option Set Name GET


/api/data/v9.0/GlobalOptionSetDefinitions(Name='metric_goaltype')

Example: Retrieve definition items by name


A common definition item that people want to retrieve are the options configured for a particular attribute. The
following example shows how to retrieve the OptionSet and GlobalOptionSet properties of a
PicklistAttributeMetadata EntityType.

NOTE
Expanding both the OptionSet and GlobalOptionSet single-valued navigation properties of PicklistAttributeMetadata
EntityType allows you to get the option definition whether the attribute is configured to use global option sets or the
'local' option set within the entity. If it is a 'local' option set, the GlobalOptionSet property will be null as shown below.
If the attribute used a global option set, the GlobalOptionSet property would contain the defined options and the
OptionSet property would be null.

Request

GET [Organization
URI]/api/data/v9.0/EntityDefinitions(LogicalName='account')/Attributes(LogicalName='accountcategorycode')/Mi
crosoft.Dynamics.CRM.PicklistAttributeMetadata?
$select=LogicalName&$expand=OptionSet($select=Options),GlobalOptionSet($select=Options) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#EntityDefinitions('account')/Attributes/Microsoft.Dynamics.CRM.PicklistAttribut
eMetadata(LogicalName,OptionSet,GlobalOptionSet,OptionSet(Options),GlobalOptionSet(Options))/$entity",
"LogicalName": "accountcategorycode",
"MetadataId": "118771ca-6fb9-4f60-8fd4-99b6124b63ad",
"[email protected]": "[Organization
URI]/api/data/v9.0/$metadata#EntityDefinitions('account')/Attributes(118771ca-6fb9-4f60-8fd4-
99b6124b63ad)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata/OptionSet(Options)/$entity",
"OptionSet": {
"Options": [{
"Value": 1,
"Label": {
"LocalizedLabels": [{
"Label": "Preferred Customer",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0bd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}],
"UserLocalizedLabel": {
"Label": "Preferred Customer",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0bd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}
},
"Description": {
"LocalizedLabels": [

],
"UserLocalizedLabel": null
},
"Color": null,
"IsManaged": true,
"MetadataId": null,
"HasChanged": null
}, {
"Value": 2,
"Label": {
"LocalizedLabels": [{
"Label": "Standard",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0dd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}],
"UserLocalizedLabel": {
"Label": "Standard",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0dd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}
},
"Description": {
"LocalizedLabels": [

],
"UserLocalizedLabel": null
},
"Color": null,
"IsManaged": true,
"MetadataId": null,
"HasChanged": null
}],
"MetadataId": "b994cdd8-5ce9-4ab9-bdd3-8888ebdb0407"
},
"GlobalOptionSet": null
}

Retrieve definition items by MetadataId


Because the MetadataId is the primary key for definition items, retrieving individual items follows the same
pattern used to retrieve business data tables.

DEF IN IT IO N IT EM EXA M P L E

Entity GET /api/data/v9.0/EntityDefinitions(<Entity


MetadataId>)

Attribute GET /api/data/v9.0/EntityDefinitions(<Entity


MetadataId>)/Attributes(<Attribute MetadataId>)
DEF IN IT IO N IT EM EXA M P L E

Relationship GET
/api/data/v9.0/RelationshipDefinitions(<Relationship
MetadataId>)

Global Option Set GET


/api/data/v9.0/GlobalOptionSetDefinitions(<OptionSet
MetadataId>)

Example: Retrieve definition items by MetadataId


To achieve the same result as shown in Example: Retrieve definition items by name, you will need to perform a
series of query operations to get the MetadataId by filtering by the entity LogicalName and then by the attribute
LogicalName .

Request

GET [Organization URI]/api/data/v9.0/EntityDefinitions?


$filter=LogicalName%20eq%20'account'&$select=MetadataId HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context":"[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions(MetadataId)","value":[
{
"MetadataId":"70816501-edb9-4740-a16c-6a5efbc05d84"
}
]
}

Request

GET [Organization URI]/api/data/v9.0/EntityDefinitions(70816501-edb9-4740-a16c-6a5efbc05d84)/Attributes?


$filter=LogicalName%20eq%20'accountcategorycode'&$select=MetadataId HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions(70816501-edb9-4740-a16c-
6a5efbc05d84)/Attributes(MetadataId)","value":[
{
"@odata.type": "#Microsoft.Dynamics.CRM.PicklistAttributeMetadata",
"MetadataId": "118771ca-6fb9-4f60-8fd4-99b6124b63ad"
}
]
}

Request

GET [Organization URI]/api/data/v9.0/EntityDefinitions(70816501-edb9-4740-a16c-


6a5efbc05d84)/Attributes(118771ca-6fb9-4f60-8fd4-
99b6124b63ad)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?
$select=LogicalName&$expand=OptionSet($select=Options),GlobalOptionSet($select=Options) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions(70816501-edb9-4740-a16c-
6a5efbc05d84)/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata(LogicalName,OptionSet,GlobalOption
Set,OptionSet(Options),GlobalOptionSet(Options))/$entity",
"LogicalName": "accountcategorycode",
"MetadataId": "118771ca-6fb9-4f60-8fd4-99b6124b63ad",
"[email protected]": "[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions(70816501-edb9-
4740-a16c-6a5efbc05d84)/Attributes(118771ca-6fb9-4f60-8fd4-
99b6124b63ad)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata/OptionSet(Options)/$entity",
"OptionSet": {
"Options": [{
"Value": 1,
"Label": {
"LocalizedLabels": [{
"Label": "Preferred Customer",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0bd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}],
"UserLocalizedLabel": {
"Label": "Preferred Customer",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0bd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}
},
"Description": {
"LocalizedLabels": [

],
"UserLocalizedLabel": null
},
"Color": null,
"IsManaged": true,
"MetadataId": null,
"HasChanged": null
}, {
"Value": 2,
"Label": {
"LocalizedLabels": [{
"Label": "Standard",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0dd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}],
"UserLocalizedLabel": {
"Label": "Standard",
"LanguageCode": 1033,
"IsManaged": true,
"MetadataId": "0dd8a218-2341-db11-898a-0007e9e17ebd",
"HasChanged": null
}
},
"Description": {
"LocalizedLabels": [

],
"UserLocalizedLabel": null
},
"Color": null,
"IsManaged": true,
"MetadataId": null,
"HasChanged": null
}],
"MetadataId": "b994cdd8-5ce9-4ab9-bdd3-8888ebdb0407"
},
"GlobalOptionSet": null
}

See also
Use the Web API with table definitions
Query table definitions using the Web API
Create and update table definitions using the Web API
Create and update table relationships using the Web API
Create and update table definitions using the Web
API
4/30/2021 • 11 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

You can perform all the same operations on table definitions using the Web API that you can with the
Organization service. This topic focuses on working with table definitions (metadata) using the Web API. To find
details about the table definition properties, see Customize table definitions and EntityMetadata EntityType.

TIP
Entities, attributes, and global option sets (also known as tables, columns, and choices) are all solution components. When
you create them you can associate them with a solution by using the MSCRM.SolutionUniqueName request header and
setting the value to the unique name of the solution it should be part of.

Create table definitions


To create a table definition, POST the JSON representation of the entity definition data to the EntityDefinitions
entity set path. The entity must include the definition for the primary name attribute. You don’t need to set
values for all the properties. The items on this list except for Description are required, although setting a
description is a recommended best practice. Property values you do not specify will be set to default values. To
understand the default values, look at the example in the Update table definitions section. The example in this
topic uses the following entity properties.

EN T IT Y M ETA DATA P RO P ERT Y VA L UE

SchemaName new_BankAccount Note: You should include the


customization prefix that matches the solution publisher.
Here the default value “new_” is used, but you should
choose the prefix that works for your solution.

DisplayName Bank Account

DisplayCollectionName Bank Accounts

Description An entity to store information about customer bank


accounts.

OwnershipType UserOwned Note: For the values you can set here, see
OwnershipTypes EnumType.

IsActivity false

HasActivities false
EN T IT Y M ETA DATA P RO P ERT Y VA L UE

HasNotes false

In addition to the properties listed previously, the EntityMetadataAttributes property must contain an array that
includes one StringAttributeMetadata EntityType to represent the primary name attribute for the entity. The
attribute IsPrimaryName property must be true. The following table describes the properties set in the example.

P RIM A RY AT T RIB UT E P RO P ERT Y VA L UE

SchemaName new_AccountName

RequiredLevel None
Note: For the values you can set here, see
AttributeRequiredLevelManagedProperty ComplexType and
AttributeRequiredLevel EnumType.

MaxLength 100

FormatName Text
Note: The primary name attribute must use Text format. For
format options available for other string attributes, see
String formats.

DisplayName Account Name

Description Type the name of the bank account.

IsPrimaryName true

NOTE
When you create or update labels using the Label ComplexType, you only need to set the LocalizedLabels property.
The UserLocalizedLabel value returned is based on the user’s language preference and is read-only.

The following example shows the creation of a custom table with the properties set. The language is English
using the locale ID (LCID) of 1033. Valid locale ID values can be found at Locale ID (LCID) Chart.
Request

POST [Organization URI]/api/data/v9.0/EntityDefinitions HTTP/1.1


Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
"Attributes": [
{
"AttributeType": "String",
"AttributeTypeName": {
"Value": "StringType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Type the name of the bank account",
"LanguageCode": 1033
}
]
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Account Name",
"LanguageCode": 1033
}
]
},
"IsPrimaryName": true,
"RequiredLevel": {
"Value": "None",
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyrequirementlevelsettings"
},
"SchemaName": "new_AccountName",
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
"FormatName": {
"Value": "Text"
},
"MaxLength": 100
}
],
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "An entity to store information about customer bank accounts",
"LanguageCode": 1033
}
]
},
"DisplayCollectionName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Bank Accounts",
"LanguageCode": 1033
}
]
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Bank Account",
"LanguageCode": 1033
}
]
},
"HasActivities": false,
"HasNotes": false,
"IsActivity": false,
"OwnershipType": "UserOwned",
"SchemaName": "new_BankAccount"
}
Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/EntityDefinitions(417129e1-207c-e511-80d2-00155d2a68d2)

Update table definitions


IMPORTANT
You can’t use the HTTP PATCH method to update data model entities. The table definitions have parity with the
Organization service UpdateEntityRequest that replaces the entity definition with the one included. Therefore, you must
use the HTTP PUT method when updating data model entities and be careful to include all the existing properties that
you don’t intend to change. You can’t update individual properties.

When you update table definitions with labels, you should include a custom MSCRM.MergeLabels header to
control how any labels in the update should be handled. If a label for an item already has labels for other
languages and you update it with a label that contains only one label for a specific language, the
MSCRM.MergeLabels header controls whether to overwrite the existing labels or merge your new label with any
existing language labels. With MSCRM.MergeLabels set to true , any new labels defined will only overwrite
existing labels when the language code matches. If you want to overwrite the existing labels to include only the
labels you include, set MSCRM.MergeLabels to false .

IMPORTANT
If you don’t include a MSCRM.MergeLabels header, the default behavior is as if the value were false and any localized
labels not included in your update will be lost.

When you update a table or column definition, you must use the PublishXml Action or PublishAllXml Action
before the changes you make will be applied to the application. More information: Publish customizations
Typically, you will retrieve the JSON definition of the entity attribute and modify the properties before you send
it back. The following example contains all the definition properties of the table created in the Create table
definitions example, but with the DisplayName changed to “Bank Business Name.” It may be useful to note that
the JSON here provides the default values for properties not set in the Create table definitions example.
Request

PUT [Organization URI]/api/data/v9.0/EntityDefinitions(417129e1-207c-e511-80d2-00155d2a68d2) HTTP/1.1


Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
MSCRM.MergeLabels: true

{
"@odata.context": "[Organization URI]/api/data/v9.0/$metadata#EntityDefinitions/$entity",
"ActivityTypeMask": 0,
"AutoRouteToOwnerQueue": false,
"CanTriggerWorkflow": true,
"Description": {
"LocalizedLabels": [
{
"Label": "An entity to store information about customer bank accounts",
"LanguageCode": 1033,
"IsManaged": false,
"IsManaged": false,
"MetadataId": "edc3abd7-c5ae-4822-a3ed-51734fdd0469",
"HasChanged": null
}
]
},
"DisplayCollectionName": {
"LocalizedLabels": [
{
"Label": "Bank Accounts",
"LanguageCode": 1033,
"IsManaged": false,
"MetadataId": "7c758e0c-e9cf-4947-93b0-50ec30b20f60",
"HasChanged": null
}
]
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Bank Business Name",
"LanguageCode": 1033
}
]
},
"EntityHelpUrlEnabled": false,
"EntityHelpUrl": null,
"IsDocumentManagementEnabled": false,
"IsOneNoteIntegrationEnabled": false,
"IsInteractionCentricEnabled": false,
"IsKnowledgeManagementEnabled": false,
"AutoCreateAccessTeams": false,
"IsActivity": false,
"IsActivityParty": false,
"IsAuditEnabled": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyauditsettings"
},
"IsAvailableOffline": false,
"IsChildEntity": false,
"IsAIRUpdated": false,
"IsValidForQueue": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyqueuesettings"
},
"IsConnectionsEnabled": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyconnectionsettings"
},
"IconLargeName": null,
"IconMediumName": null,
"IconSmallName": null,
"IsCustomEntity": true,
"IsBusinessProcessEnabled": false,
"IsCustomizable": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "iscustomizable"
},
"IsRenameable": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "isrenameable"
},
"IsMappable": {
"IsMappable": {
"Value": true,
"CanBeChanged": false,
"ManagedPropertyLogicalName": "ismappable"
},
"IsDuplicateDetectionEnabled": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyduplicatedetectionsettings"
},
"CanCreateAttributes": {
"Value": true,
"CanBeChanged": false,
"ManagedPropertyLogicalName": "cancreateattributes"
},
"CanCreateForms": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "cancreateforms"
},
"CanCreateViews": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "cancreateviews"
},
"CanCreateCharts": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "cancreatecharts"
},
"CanBeRelatedEntityInRelationship": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canberelatedentityinrelationship"
},
"CanBePrimaryEntityInRelationship": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canbeprimaryentityinrelationship"
},
"CanBeInManyToMany": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canbeinmanytomany"
},
"CanEnableSyncToExternalSearchIndex": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canenablesynctoexternalsearchindex"
},
"SyncToExternalSearchIndex": false,
"CanModifyAdditionalSettings": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyadditionalsettings"
},
"CanChangeHierarchicalRelationship": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canchangehierarchicalrelationship"
},
"IsOptimisticConcurrencyEnabled": true,
"ChangeTrackingEnabled": false,
"IsImportable": true,
"IsIntersect": false,
"IsMailMergeEnabled": {
"Value": true,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifymailmergesettings"
},
},
"IsManaged": false,
"IsEnabledForCharts": true,
"IsEnabledForTrace": false,
"IsValidForAdvancedFind": true,
"IsVisibleInMobile": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifymobilevisibility"
},
"IsVisibleInMobileClient": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifymobileclientvisibility"
},
"IsReadOnlyInMobileClient": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifymobileclientreadonly"
},
"IsOfflineInMobileClient": {
"Value": false,
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifymobileclientoffline"
},
"DaysSinceRecordLastModified": 0,
"IsReadingPaneEnabled": true,
"IsQuickCreateEnabled": false,
"LogicalName": "new_bankaccount",
"ObjectTypeCode": 10009,
"OwnershipType": "UserOwned",
"PrimaryNameAttribute": "new_accountname",
"PrimaryImageAttribute": null,
"PrimaryIdAttribute": "new_bankaccountid",
"Privileges": [
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvCreatenew_BankAccount",
"PrivilegeId": "d1a8de4b-27df-42e1-bc5c-b863e002b37f",
"PrivilegeType": "Create"
},
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvReadnew_BankAccount",
"PrivilegeId": "726043b1-de2c-487e-9d6d-5629fca2bf22",
"PrivilegeType": "Read"
},
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvWritenew_BankAccount",
"PrivilegeId": "fa50c539-b6c7-4eaf-bd49-fd8224bc51b6",
"PrivilegeType": "Write"
},
{
"CanBeBasic": true,
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvDeletenew_BankAccount",
"PrivilegeId": "17c1fd6e-f856-45e7-b563-796f53108b85",
"PrivilegeType": "Delete"
},
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvAssignnew_BankAccount",
"PrivilegeId": "133ca81d-668e-4c19-a71e-10c6dfe099cd",
"PrivilegeType": "Assign"
},
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvSharenew_BankAccount",
"PrivilegeId": "15f27df4-9c67-47c9-b1f1-274e1c44f24a",
"PrivilegeType": "Share"
},
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvAppendnew_BankAccount",
"PrivilegeId": "ac8b1920-8f93-4e9d-94e3-c680e2a2f228",
"PrivilegeType": "Append"
},
{
"CanBeBasic": true,
"CanBeDeep": true,
"CanBeGlobal": true,
"CanBeLocal": true,
"CanBeEntityReference": false,
"CanBeParentEntityReference": false,
"Name": "prvAppendTonew_BankAccount",
"PrivilegeId": "f63a5f46-3bc7-4eac-81d0-7f77f566ef46",
"PrivilegeType": "AppendTo"
}
],
"RecurrenceBaseEntityLogicalName": null,
"ReportViewName": "Filterednew_BankAccount",
"SchemaName": "new_BankAccount",
"IntroducedVersion": "1.0",
"IsStateModelAware": true,
"EnforceStateTransitions": false,
"EntityColor": null,
"LogicalCollectionName": "new_bankaccounts",
"CollectionSchemaName": "new_BankAccounts",
"EntitySetName": "new_bankaccounts",
"IsEnabledForExternalChannels": false,
"IsPrivate": false,
"MetadataId": "417129e1-207c-e511-80d2-00155d2a68d2",
"HasChanged": null
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Create columns
You can create table columns (entity attributes) at the same time you create the table definition by including the
JSON definition of the attributes in the Attributes array for the entity you post in addition to the string
attribute that serves as the primary name attribute. If you want to add attributes to an entity that is already
created, you can send a POST request including the JSON definition of them to the entity Attributes collection-
valued navigation property.

Create a string column


The following example will use these properties to create a string attribute.

ST RIN G AT T RIB UT E P RO P ERT IES VA L UES

SchemaName new_BankName

DisplayName Bank Name

Description Type the name of the bank.

RequiredLevel None

MaxLength 100

FormatName Text

The following example creates a string attribute using the properties and adds it to the entity with the
MetadataId value of 402fa40f-287c-e511-80d2-00155d2a68d2.
The URI for the attribute is returned in the response.
Request
POST [Organization URI]/api/data/v9.0/EntityDefinitions(402fa40f-287c-e511-80d2-00155d2a68d2)/Attributes
HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"AttributeType": "String",
"AttributeTypeName": {
"Value": "StringType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Type the name of the bank",
"LanguageCode": 1033
}
]
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Bank Name",
"LanguageCode": 1033
}
]
},
"RequiredLevel": {
"Value": "None",
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyrequirementlevelsettings"
},
"SchemaName": "new_BankName",
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
"FormatName": {
"Value": "Text"
},
"MaxLength": 100
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/EntityDefinitions(402fa40f-287c-e511-80d2-
00155d2a68d2)/Attributes(f01bef16-287c-e511-80d2-00155d2a68d2)

Create a Money column


The following example will use these properties to create a money attribute.

M O N EY AT T RIB UT E P RO P ERT IES VA L UES

SchemaName new_Balance

DisplayName Balance
M O N EY AT T RIB UT E P RO P ERT IES VA L UES

Description Enter the balance amount.

RequiredLevel None

PrecisionSource 2
Note: For information on the valid values for
PrecisionSource, see MoneyType. The value 2 means that the
level of decimal precision will match
TransactionCurrency.CurrencyPrecision that is associated
with the current record.

The following example creates a money attribute using the properties and adds it to the entity with the
MetadataId value of 402fa40f-287c-e511-80d2-00155d2a68d2. The URI for the attribute is returned in the
response.
Request

POST [Organization URI]/api/data/v9.0/EntityDefinitions(402fa40f-287c-e511-80d2-00155d2a68d2)/Attributes


HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"AttributeType": "Money",
"AttributeTypeName": {
"Value": "MoneyType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Enter the balance amount",
"LanguageCode": 1033
}
]
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Balance",
"LanguageCode": 1033
}
]
},
"RequiredLevel": {
"Value": "None",
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyrequirementlevelsettings"
},
"SchemaName": "new_Balance",
"@odata.type": "Microsoft.Dynamics.CRM.MoneyAttributeMetadata",
"PrecisionSource": 2
}

Response
HTTP/1.1 204 No Content
OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/EntityDefinitions(402fa40f-287c-e511-80d2-
00155d2a68d2)/Attributes(f11bef16-287c-e511-80d2-00155d2a68d2)

Create a datetime column


The following example will use these properties to create a datetime attribute.

DAT ET IM E AT T RIB UT E P RO P ERT IES VA L UES

SchemaName new_Checkeddate

DisplayName Date

Description The date the account balance was last confirmed.

RequiredLevel None

Format DateOnly Note: For the valid options for this property, see
DateTimeFormat EnumType.

The following example creates a datetime attribute using the properties and adds it to the entity with the
MetadataId value of 402fa40f-287c-e511-80d2-00155d2a68d2. The URI for the attribute is returned in the
response.
Request
POST [Organization URI]/api/data/v9.0/EntityDefinitions(402fa40f-287c-e511-80d2-00155d2a68d2)/Attributes
HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"AttributeType": "DateTime",
"AttributeTypeName": {
"Value": "DateTimeType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "The date the account balance was last confirmed",
"LanguageCode": 1033
}
]
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Date",
"LanguageCode": 1033
}
]
},
"RequiredLevel": {
"Value": "None",
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyrequirementlevelsettings"
},
"SchemaName": "new_Checkeddate",
"@odata.type": "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata",
"Format": "DateOnly"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/EntityDefinitions(402fa40f-287c-e511-80d2-
00155d2a68d2)/Attributes(fe1bef16-287c-e511-80d2-00155d2a68d2)

Create a customer lookup column


Unlike other attributes, a customer lookup attribute is created using the CreateCustomerRelationships Action.
The parameters for this action require the definition of the lookup attribute and a pair of one-to-many
relationships. A customer lookup attribute has two one-to-many relationships: one to the account entity and the
other one to contact entity.
The following example will use these properties to create a customer lookup attribute.

C USTO M ER LO O K UP AT T RIB UT E P RO P ERT IES VA L UES

SchemaName new_CustomerId
C USTO M ER LO O K UP AT T RIB UT E P RO P ERT IES VA L UES

DisplayName Customer

Description Sample Customer Lookup Attribute

The example creates a customer lookup attribute, new_CustomerId , and adds it to the custom entity:
new_bankaccount . The response is a CreateCustomerRelationshipsResponse ComplexType.

Request

POST [Organization URI]/api/data/v9.0/CreateCustomerRelationships HTTP/1.1


OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8

{
"OneToManyRelationships": [{
"SchemaName": "new_bankaccount_customer_account",
"ReferencedEntity": "account",
"ReferencingEntity": "new_bankaccount"
}, {
"SchemaName": "new_bankaccount_customer_contact",
"ReferencedEntity": "contact",
"ReferencingEntity": "new_bankaccount"
}],
"Lookup": {
"AttributeType": "Lookup",
"AttributeTypeName": {
"Value": "LookupType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Sample Customer Lookup Attribute",
"LanguageCode": 1033
}],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Sample Customer Lookup Attribute",
"LanguageCode": 1033
}
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Customer",
"LanguageCode": 1033
}],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Customer",
"LanguageCode": 1033
}
},
"SchemaName": "new_CustomerId",
"@odata.type": "Microsoft.Dynamics.CRM.ComplexLookupAttributeMetadata"
}
}

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
"@odata.context": "[Organization
URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.CreateCustomerRelationshipsResponse",
"RelationshipIds": [
"a7d261bc-3580-e611-80d7-00155d2a68de", "aed261bc-3580-e611-80d7-00155d2a68de"
],
"AttributeId": "39a5d94c-e8a2-4a41-acc0-8487242d455e"
}

Update a column
As mentioned in Update table definitions, data model entities are updated using the HTTP PUT method with the
entire JSON definition of the current item. This applies to entity attributes as well as entities. Just like with
entities, you have the option to overwrite labels using the MSCRM.MergeLabels header with the value set to
false , and you must publish customizations before they are active in the system.

See also
Use the Web API with Microsoft Dataverse metadata
Query table definitions using the Web API
Retrieve table definitions by name or MetadataId
Model table relationships using the Web API
Work with table definitions using the Organization service
Column (attribute) definitions
Create and update table relationships using the
Web API
7/19/2021 • 4 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

The Web API supports working with relationship definitions (metadata). The concepts described in Table
relationship definitions also apply to the Web API.

Eligibility for relationships


Before you create a table (entity) relationship you should confirm whether the table is eligible to participate in
the relationship. You can use the actions listed in the following table to determine eligibility. These actions
correspond to the Organization service messages described in Table relationship eligibility.

A C T IO N DESC RIP T IO N

CanBeReferenced Action Checks whether the specified entity can be the primary
entity (one) in a one-to-many relationship.

CanBeReferencing Action Checks whether the specified entity can be the referencing
entity (many) in a one-to-many relationship.

CanManyToMany Action Checks whether the entity can participate in a many-to-


many relationship.

GetValidManyToMany Function Returns the set of entities that can participate in a many-to-
many relationship.

GetValidReferencedEntities Function Returns the set of entities that are valid as the primary entity
(one) from the specified entity in a one-to-many
relationship.

GetValidReferencingEntities Function Returns the set of entities that are valid as the related entity
(many) to the specified entity in a one-to-many relationship.

Create a one-to-many relationship


When you create a one-to-many relationship, you define it by using the OneToManyRelationshipMetadata
EntityType. This definition includes the lookup attribute, which is defined by using LookupAttributeMetadata
EntityType and also requires complex properties using AssociatedMenuConfiguration ComplexType,
CascadeConfiguration ComplexType, Label ComplexType and LocalizedLabel ComplexType. The lookup attribute
is set to the Lookup single-valued navigation property of the OneToManyRelationshipMetadata object and gets
created at the same time using deep insert. More information: Create related tables in one operation and Table
relationship metadata
If you want to apply a custom navigation property name for a one-to-many relationship you can set values for
the ReferencingEntityNavigationPropertyName and ReferencedEntityNavigationPropertyName properties.
Once you have generated the necessary JSON to define the relationship and the lookup attribute, POST the
JSON to the RelationshipDefinitions entity set. You must include the @odata.type property value of
Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata to clarify the type of relationship you’re creating because
this same entity set is used to create many-to-many relationships. The uri for the resulting relationship is
returned in the response.
Request

POST [Organization URI]/api/data/v9.0/RelationshipDefinitions HTTP/1.1


Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"SchemaName": "new_contact_new_bankaccount",
"@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
"AssociatedMenuConfiguration": {
"Behavior": "UseCollectionName",
"Group": "Details",
"Label": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Bank Accounts",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Bank Accounts",
"LanguageCode": 1033
}
},
"Order": 10000
},
"CascadeConfiguration": {
"Assign": "Cascade",
"Delete": "Cascade",
"Merge": "Cascade",
"Reparent": "Cascade",
"Share": "Cascade",
"Unshare": "Cascade"
},
"ReferencedAttribute": "contactid",
"ReferencedEntity": "contact",
"ReferencingEntity": "new_bankaccount",
"Lookup": {
"AttributeType": "Lookup",
"AttributeTypeName": {
"Value": "LookupType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "The owner of the account",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "The owner of the account",
"LanguageCode": 1033
}
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Account Owner",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Account Owner",
"LanguageCode": 1033
}
},
"RequiredLevel": {
"Value": "ApplicationRequired",
"CanBeChanged": true,
"ManagedPropertyLogicalName": "canmodifyrequirementlevelsettings"
},
"SchemaName": "new_AccountOwner",
"@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata"
}
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/RelationshipDefinitions(d475020f-5d7c-e511-80d2-
00155d2a68d2)

Create a many-to-many relationship


If you want to apply a custom navigation property name for a many-to-many relationship, you can set values
for the Entity1NavigationPropertyName and Entity2NavigationPropertyName properties.
Once you have generated the necessary JSON to define the relationship, POST the JSON to the
RelationshipDefinitions entity set. You must include the @odata.type property value of
Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata to clarify the type of relationship you’re creating
because this same entity set is used to create one-to-many relationships. The URI for the resulting relationship is
returned in the response.
Request
POST [Organization URI]/api/data/v9.0/RelationshipDefinitions HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"SchemaName": "new_accounts_campaigns",
"@odata.type": "Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata",
"Entity1AssociatedMenuConfiguration": {
"Behavior": "UseLabel",
"Group": "Details",
"Label": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Account",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Account",
"LanguageCode": 1033
}
},
"Order": 10000
},
"Entity1LogicalName": "account",
"Entity2AssociatedMenuConfiguration": {
"Behavior": "UseLabel",
"Group": "Details",
"Label": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Campaign",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Campaign",
"LanguageCode": 1033
}
},
"Order": 10000
},
"Entity2LogicalName": "campaign",
"IntersectEntityName": "new_accounts_campaigns"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: [Organization URI]/api/data/v9.0/RelationshipDefinitions(420245fa-c77c-e511-80d2-
00155d2a68d2)

Create relationships to support a multi-table lookup


Multi-table lookup type columns allow a user to use a specific table that has multiple one-to-many (1:M)
relationships to other tables in the environment. A single lookup type column can refer to multiple other tables.
A lookup value submitted to the multi-table type column will be matched to a record in any of the related tables.
More information: Use multi-table lookup columns

Update relationships
As discussed in Update table definitions, you update relationships using the HTTP PUT method to replace the
existing definition with changes you want to apply. You can’t edit individual properties using the HTTP PATCH
method as you can with business data tables. Just like with entities and attributes, you should include a
MSCRM.MergeLabels header with the value set to true to avoid overwriting localized labels not included in your
update and you must publish customizations before they are active in the system.

Delete relationships
To delete a relationship using the Web API, use the HTTP DELETE method with the URI for the relationship.
See also
Use the Web API with table definitions
Query table definitions using the Web API
Retrieve table definitions by name or MetadataId
Model tables and columns using the Web API
Multi-table Lookups
7/19/2021 • 3 minutes to read • Edit Online

Multi-table lookup type columns allow a user to use a specific table that has multiple one-to-many (1:M)
relationships to other tables in the environment. A single lookup type column can refer to multiple other tables.
A lookup value submitted to the multi-table type column will be matched to a record in any of the related tables.
Multi-table types are currently built into Microsoft Dataverse as static types like Customer, which connects to
Account and Contact. This new feature gives users the power to define any other multi-table lookups they may
need.

NOTE
At this time users can create and modify custom multi-table lookups via the SDK or Web APIs. Interactive user interface
support will be coming in a future release.

Examples
Let's say you are hosting media for users in a library. You have many different MediaObjects, many of them have
the same name but are in different formats like “Books”, “Audio”, and “Video”. Creating a multi-table lookup
called “new_Media” that has 1:M relationships to “new_Books”, “new_Audio”, and “new_Video” will result in a
“new_Media” lookup table that provides quick identifications of records stored in specific tables.
new_Media lookup table
P RIM A RY ID P RIM A RY N A M E REL AT EDID REL AT ED N A M E

<media1> MediaObjectOne <books1> Content1

<media2> MediaObjectTwo <audio1> Content1

<media3> MediaObjectThree <video1> Content3

<media4> MediaObjectFour <audio2> Content3

new_Books table
P RIM A RY ID P RIM A RY N A M E C A L L N UM B ER

<books1> Content1 1ww-3452

<books2> Content2 a4e-87hw

new_Audio table
P RIM A RY ID P RIM A RY N A M E A UDIO F O RM AT

<audio1> Content1 mp4

<audio2> Content3 wma


new_Video table
P RIM A RY ID P RIM A RY N A M E VIDEO F O RM AT

<video1> Content3 wmv

<video2> Content2 avi

The Media look up can return records across all the tables in the polymorphic lookup.
A lookup on Media with the name Content1 would retrieve records for <books1> and <audio1>
A lookup on Media of Content3 would retrieve records for <audio2> and <video1>
Web API example
Shown below is an HTTP post for a polymorphic lookup attribute.

POST [Organization URI]/api/data/v9.0/CreatePolymorphicLookupAttribute HTTP/1.1

Accept: application/json
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0

{
"OneToManyRelationships": [
{
"SchemaName": "new_media_new_book",
"ReferencedEntity": "new_book",
"ReferencingEntity": "new_media"
},
{
"SchemaName": "new_media_new_video",
"ReferencedEntity": "new_video",
"ReferencingEntity": "new_media"
},
{
"SchemaName": "new_media_new_audio",
"ReferencedEntity": "new_audio",
"ReferencingEntity": "new_media",
"CascadeConfiguration": {
"Assign": "NoCascade",
"Delete": "RemoveLink",
"Merge": "NoCascade",
"Reparent": "NoCascade",
"Share": "NoCascade",
"Unshare": "NoCascade"
}
}
],

"Lookup": {
"AttributeType": "Lookup",
"AttributeTypeName": {
"Value": "LookupType"
},

"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Media Polymorphic Lookup",
"LanguageCode": 1033
}
],
],

"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": " Media Polymorphic Lookup Attribute",
"LanguageCode": 1033
}
},

"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "MediaPolymorphicLookup",
"LanguageCode": 1033
}
],

"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "MediaPolymorphicLookup",
"LanguageCode": 1033
}
},

"SchemaName": "new_mediaPolymporphicLookup",
"@odata.type": "Microsoft.Dynamics.CRM.ComplexLookupAttributeMetadata"
}
}

The response from the HTTP post is shown below containing the ID of the polymorphic attribute and all the
relationships created.

{
"@odata.context":
"http://<organization
URL>/api/data/v9.1/$metadata#Microsoft.Dynamics.CRM.CreatePolymorphicLookupAttributeResponse",

"RelationshipIds":[
"77d4c6e9-0397-eb11-a81c-000d3a6cfaba",
"7ed4c6e9-0397-eb11-a81c-000d3a6cfaba",
"85d4c6e9-0397-eb11-a81c-000d3a6cfaba"
],

"AttributeId":"d378dd3e-42f4-4bd7-95c7-0ee546c7de40"

Use the multi-table lookup API's


The following table lists the operations relevant for table and attribute definitions.

O P ERAT IO N
( M ET H O D) DESC RIP T IO N URL F O RM AT

Create New API [OrganizationUrl]/api/data/v9.0


(POST) /CreatePolymorphicLookupAttribute

Retrieve attribute Existing API [OrganizationUrl]/api/data/v9.0


(GET) /EntityDefinitions(<EntityId>)/Attribut
es(<AttributeId>)
O P ERAT IO N
( M ET H O D) DESC RIP T IO N URL F O RM AT

Retrieve relationship Existing API [OrganizationUrl]/api/data/v9.0


(GET) /RelationshipDefinitions(<RelationshipI
d>)

Add relationship Adds a relationship [OrganizationUrl]/api/data/v9.0


(POST) to an existing /RelationshipDefinitions
polymorphic lookup
attribute

Remove relationship Existing API [OrganizationUrl]/api/data/v9.0


(DELETE) /RelationshipDefinitions(<RelationshipI
d>)

Remove attribute Existing API [OrganizationUrl]/api/data/v9.0


(DELETE) /EntityDefinitions(<EntityId>)/Attribut
es(<AttributeId>)

The following table lists the operations relevant for table and attribute data.

O P ERAT IO N
( M ET H O D) DESC RIP T IO N URL F O RM AT

Create See the "new_checkouts" example [OrganizationUrl]/api/data/v9.0


(POST) below /<entitysetName>

Retrieve Add the following header to get [OrganizationUrl]/api/data/v9.0


(GET) annotations: /<entitysetName>(<recordId>)
Content-Type: application/json
Prefer: odata.include-
annotations="*"

Below is an example request that creates a new entityset with 2 rows.

POST [OrganizationUrl]/api/data/v9.1/new_checkouts

{
"new_name": "c1",
[email protected]: "/new_books(387a2c9b-ecc6-ea11-a81e-000d3af68bd7)"
}

{
"new_name": "c2",
[email protected]: "/new_devices(6472e7ba-ecc6-ea11-a81e-000d3af68bd7)"
}

Create polymorphic lookup (example payload)

POST [OrganizationUrl]/api/data/v9.0/CreatePolymorphicLookupAttribute
{
"OneToManyRelationships": [
{
"SchemaName": "new_checkout_poly_new_book",
"ReferencedEntity": "new_book",
"ReferencingEntity": "new_checkout"
},
{
"SchemaName": "new_checkout_poly_new_device",
"ReferencedEntity": "new_device",
"ReferencingEntity": "new_checkout"
},
{
"SchemaName": "new_checkout_poly_new_dvd",
"ReferencedEntity": "new_dvd",
"ReferencingEntity": "new_checkout",
"CascadeConfiguration": {
"Assign": "NoCascade",
"Delete": "RemoveLink",
"Merge": "NoCascade",
"Reparent": "NoCascade",
"Share": "NoCascade",
"Unshare": "NoCascade"
}
}
],
"Lookup": {
"AttributeType": "Lookup",
"AttributeTypeName": {
"Value": "LookupType"
},
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkouted item Polymorphic Lookup Attribute",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkedout item Polymorphic Lookup Attribute",
"LanguageCode": 1033
}
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkedout item",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkedout item",
"LanguageCode": 1033
}
},
"SchemaName": "new_CheckedoutItem",
"@odata.type": "Microsoft.Dynamics.CRM.ComplexLookupAttributeMetadata"
}
}
Add relationship to existing polymorphic lookup (example payload)

POST [OrganizationUrl]/api/data/v9.0/RelationshipDefinitions

{
"SchemaName": "new_checkout_poly_new_researchresource",
"@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
"CascadeConfiguration": {
"Assign": "NoCascade",
"Delete": "RemoveLink",
"Merge": "NoCascade",
"Reparent": "NoCascade",
"Share": "NoCascade",
"Unshare": "NoCascade"
},
"ReferencedEntity": "new_researchresource",
"ReferencingEntity": "new_checkout",
"Lookup": {
"AttributeType": "Lookup",
"AttributeTypeName": { "Value": "LookupType" },
"Description": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkout Polymorphic Lookup Attribute",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkout Polymorphic Lookup Attribute",
"LanguageCode": 1033
}
},
"DisplayName": {
"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [
{
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkout item",
"LanguageCode": 1033
}
],
"UserLocalizedLabel": {
"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Checkout item",
"LanguageCode": 1033
}
},
"SchemaName": "new_CheckedoutItem",
"@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata"
}
}

See Also
Create and update entity relationships
Create and update choices (option sets) using the
Web API
4/30/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Typically, you use global option sets to set table columns so that different columns can share the same set of
options, which are maintained in one location. Unlike local option sets which are defined only for a specific
column, you can reuse global option sets. You will also see them used in request parameters in a manner similar
to an enumeration.
When you define a global option set using a POST request to [Organization URI]
/api/data/v9.0/GlobalOptionSetDefinitions , we recommend that you let the system assign a value. You do this
by passing a null value when you create the new OptionMetadata instance. When you define an option, it will
contain an option value prefix specific to the context of the publisher set for the solution that the option set is
created in. This prefix helps reduce the chance of creating duplicate option sets for a managed solution, and in
any option sets that are defined in environments where your managed solution is installed. For more
information, see Merge option set options.

Messages
The following table lists the messages that you can use with global option sets.

M ESSA GE W EB A P I O P ERAT IO N

CreateOptionSet Use POST request to [Organization URI]


/api/data/v9.0/GlobalOptionSetDefinitions .

DeleteOptionSet Use DELETE request to [Organization URI]


/api/data/v9.0/GlobalOptionSetDefinitions(
metadataid ) .

RetrieveAllOptionSets Use GET request to [Organization URI]


/api/data/v9.0/GlobalOptionSetDefinitions .

RetrieveOptionSet Use GET request to [Organization URI]


/api/data/v9.0/GlobalOptionSetDefinitions(
metadataid ) .

The following table lists the messages you can use with local and global option sets

M ESSA GE W EB A P I O P ERAT IO N

DeleteOptionValue DeleteOptionValue Action /


Deletes one of the values in a global option set.
M ESSA GE W EB A P I O P ERAT IO N

InsertOptionValue InsertOptionValue Action /


Inserts a new option into a global option set.

InsertStatusValue InsertStatusValue Action /


Inserts a new option into the global option set used in
the Status column.

OrderOption OrderOption Action /


Changes the relative order of the options in an option
set.

UpdateOptionSet Use PUT request with a OptionSetMetadataBase EntityType


/ to [Organization URI]
/api/data/v9.0/GlobalOptionSetDefinitions(
metadataid )
Only those properties defined by the
OptionSetMetadataBase can be updated. This does not
include the options. Use other actions to make changes to
options.

UpdateOptionValue UpdateOptionValue Action /


Updates an option in a global option set.

UpdateStateValue UpdateStateValue Action /


Inserts a new option into the option set used in the
Status column.

See also
Customize choices
Use Postman with the Web API
4/30/2021 • 2 minutes to read • Edit Online

There are a number of third-party tools that allow you to authenticate to Microsoft Dataverse instances and to
compose and send Web API requests and view responses. Postman is one of the most popular.
Use Postman to perform ad hoc queries or to verify the behavior of operations without writing a program. This
section covers information on how to configure a Postman environment that connects to your Dataverse
instance and use Postman to perform operations with the Web API.
Postman offers many other capabilities beyond those covered in this content. More information: First 5 things to
try if you're new to Postman

In this section
Set up a Postman environment
Use Postman to perform operations with the Web API
Set up a Postman environment
10/4/2021 • 3 minutes to read • Edit Online

You can use Postman to connect to your Microsoft Dataverse instance and to compose Web API requests, send
them, and view responses. Managing authentication challenges many people. This topic describes how to
configure a Postman environment to work for your Dataverse environments.
You can use a Postman environment to save a set of variables that you use to connect. These values can be
accessed within Postman by using this syntax: {{name}} . For more information with Postman variables, see
Postman Documentation > Variables.

Prerequisites
Have a Power Apps Dataverse environment that you can connect to.
Download and install the Postman desktop application.

Connect with your Dataverse environment


This environment uses a client ID for an application that is registered for all Dataverse environments.
You can use the clientid and callback values supplied in these instructions. However, when building your
own application, you should register your own Azure Active Directory (Azure AD) application.
To register your own Azure AD application, see the steps described in Walkthrough: Register a Dataverse app
with Azure Active Directory.
Use these steps to create a Postman environment that you can use to connect with your Dataverse instance:
1. Launch the Postman desktop application.
2. Select the Environment Options gear icon in the top-right corner.
3. In the Manage Environments dialog box, select the Add button to add a new environment.
1. In the dialog box that opens, type a name for the environment. Then add the following key-value pairs
into the editing space.

VA RIA B L E N A M E VA L UE

url https://<add your environment name, like


'myorg.crm'>.dynamics.com

clientid 51f81489-12ee-4a9e-aaae-a2591f45987d

version 9.0

webapiurl {{url}}/api/data/v{{version}}/

callback https://callbackurl

authurl https://login.microsoftonline.com/common/oauth2/authorize?
resource={{url}}

NOTE
For Dataverse search, specify a version of 1.0 and a webapiurl of {{url}}/api/search/v{{version}}/.
2. Replace the instance URL placeholder value with the URL of your Dataverse environment, and select Add
to save the environment.
3. Close the Manage environments dialog box.
Generate an access token to use with your environment
To connect using OAuth 2.0 , you must have an access token. Use the following steps to get a new access token:
1. Make sure the new environment you created is selected.
2. Select the Authorization tab.
3. Set the Type to OAuth 2.0 .
4. Verify that you have selected the environment that you created.
5. Select Get New Access Token
6. Set the following values in the dialog box. Select Implicit from the Grant Type drop-down menu. You
can set the Token Name to whatever you like, and leave other keys set to default values.

NOTE
If you are configuring environments in Postman for multiple Dataverse instances using different user credentials,
you might need to delete the cookies cached by Postman. Select the Cookies link, which can be found under the
Send button, and remove the saved cookies from the Manage Cookies dialog box.

Some of these cookies are very persistent. You can delete some of them in groups, but you might have to delete
others individually. You might need to do this twice to ensure that no cookies remain.
7. Select Request Token . When you do this, an Azure Active Directory sign-in page appears. Enter your
username and password.
8. After the token is generated, scroll to the bottom and select Use Token . This closes the Manage Access
Tokens dialog box.
9. After you have added a token, you can select which token to apply to requests. On the Available Tokens
drop-down list, select the token you have just created. The Authorization header gets added to the Web
API request.
See Test your connection for steps to verify your connection.

Test your connection


Create a new Web API request to test the connection with your Dataverse instance. Use the WhoAmI function:
1. Select GET as the HTTP method and add {{webapiurl}}WhoAmI in the editing space.

2. Select Send to send this request.


3. If your request is successful, you see the data from the WhoAmIResponse ComplexType that is returned by
the WhoAmI Function.

See also
Use Postman to perform operations
Walkthrough: Register a Dataverse app with Azure Active Directory
Use Postman to perform operations with the Web
API
7/19/2021 • 3 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Use Postman to compose and send Web API requests and view responses. This topic describes how to use
Postman to create Web API requests that perform create, retrieve, update, and delete (CRUD) operations and use
functions and actions.

IMPORTANT
You need to have an environment that was created by using the steps described in Set up a Postman environment.

The environment created by using the instructions in Set up a Postman environment creates a {{webapiurl}}
Postman variable that provides the base URL for requests. Append to this variable to define the URL for your
requests.
The HTTP methods and values you use depend on the type of operations you want to perform. The following
sections show examples of common operations.

Retrieve multiple records


Use a GET request to retrieve a set of records. The following example retrieves the first three account records.

NOTE
Web API requests should include certain HTTP headers. Every request should include the Accept header value of
application/json , even when no response body is expected. The current OData version is 4.0 , so include header
OData-Version: 4.0 . Include the OData-MaxVersion header so that there is no ambiguity about the version when
there are new releases of OData. More information: HTTP headers.

Example
GET {{webapiurl}}accounts?$select=name,accountnumber&$top=3

The body of the response looks like this:


{
"@odata.context":
"https://yourorg.crm.dynamics.com/api/data/v9.0/$metadata#accounts(name,accountnumber)",
"value": [
{
"@odata.etag": "W/\"2291741\"",
"name": "Contoso Ltd",
"accountnumber": null,
"accountid": "9c706dc8-d2f5-e711-a956-000d3a328903"
},
{
"@odata.etag": "W/\"2291742\"",
"name": "Fourth Coffee",
"accountnumber": null,
"accountid": "a2706dc8-d2f5-e711-a956-000d3a328903"
},
{
"@odata.etag": "W/\"2291743\"",
"name": "Contoso Ltd",
"accountnumber": null,
"accountid": "9c3216b8-3efb-e711-a957-000d3a328903"
}
]
}

More information: Query data using the Web API.

Retrieve a particular record


Use a GET request to retrieve a record. The following example retrieves two properties from a specific account
and expands information about the related primary contact to include a full name.
GET {{webapiurl}}accounts( <accountid>
)?$select=name,accountnumber&$expand=primarycontactid($select=fullname)

The body of the response looks like this:

{
"@odata.context":
"https://yourorg.crm.dynamics.com/api/data/v9.0/$metadata#accounts(name,accountnumber,primarycontactid(fulln
ame))/$entity",
"@odata.etag": "W/\"2291742\"",
"name": "Fourth Coffee",
"accountnumber": null,
"accountid": "a2706dc8-d2f5-e711-a956-000d3a328903",
"primarycontactid": {
"@odata.etag": "W/\"1697263\"",
"fullname": "Susie Curtis",
"contactid": "a3706dc8-d2f5-e711-a956-000d3a328903"
}
}

More information: Retrieve a table using the Web API.

Create a record
Use a POST request to send data to create a record. Set the URL to the entity set name--in this case, accounts --
and set the headers as shown here.
POST {{webapiurl}}accounts
Set the body of the request with information about the account to create.

When you send this request, the body will be empty, but the ID of the created account will be in the
OData-EntityId header value.

More information: Create a table using the Web API.

Update a record
Use the PATCH method to update a table record, as shown here.
PATCH {{webapiurl}}accounts( <accountid> )

When you send this request, the response body will be empty, but the ID of the updated account will be in the
OData-EntityId header value.

More information: Update and delete tables using the Web API.

Delete a record
Use the DELETE method to delete an existing record.
DELETE {{webapiurl}}accounts( <accountid> )

When you send this request, the account record with the given accountid gets deleted.
More information: Update and delete tables using the Web API.

Use a function
Use a GET request with the functions listed in Web API Function Reference to perform reusable operations with
the Web API. The example that follows shows how to send a Web API request that uses the RetrieveDuplicates
function / to detect and retrieve duplicates of a specified record.
H T T P M ET H O D URL

GET {{webapiurl}}RetrieveDuplicates(BusinessEntity=@p1,MatchingEntityName=@p2,PagingInfo=
@p1={'@odata.type':'Microsoft.Dynamics.CRM.account','accountid':'
<accountid>
'}&@p2='account'&@p3={'PageNumber':1,'Count':50}

Functions return either a collection or a complex type. The response from the preceding RetrieveDuplicates
function should look like this:

{
{
"@odata.context": "https://yourorgname.crm.dynamics.com/api/data/v9.0/$metadata#accounts",
"value": [
<Omitted for brevity: JSON data for any matching accounts including all properties>
]
}
}

More information: Use Web API functions.

Use an action
Use a POST request with the actions listed in Web API Action Reference to perform operations that have side
effects.
This example shows how to use BulkDetectDuplicates action.
POST {{webapiurl}}BulkDetectDuplicates

The request in the example just shown submits an asynchronous duplicate detection job that runs in the
background. The duplicates are detected according to the published duplicate rules for the table type.
BulkDetectDuplicatesResponse ComplexType is returned as a response from BulkDetectDuplicates action. The
response includes the JobId property, which contains the GUID of the asynchronous duplicate detection job
that detects and logs duplicate records.
More information: Use Web API actions.

See also
Use Postman with the Web API
Perform operations using the Web API
Client-side JavaScript using Web API in model-
driven apps
5/21/2021 • 2 minutes to read • Edit Online

In HTML web resources, form scripts, or ribbon commands in model-driven apps, you can use JavaScript to
perform operations on Microsoft Dataverse data using the Web API. Use the Xrm.WebApi client API methods to
use Web API with JavaScript and web resources.
See also
Apply business logic using client scripting
Customize commands and ribbon
Web resources
Microsoft Dataverse Web API Versions
6/15/2021 • 2 minutes to read • Edit Online

Beginning with the v9.0 release of Dynamics 365, the Web API supports version specific differences in the same
environment.
This is different from the behavior for in the v8.x releases. In the previous releases new capabilities were
available to any version of the service depending on the update applied to the environment. After an upgrade to
v8.2, the v8.0, and v8.1 services were all identical. This was possible because all the changes were additive.
Nothing was removed or introduced breaking changes. As a result, the specific version referenced in the service
URL for the v8.x wasn't actually important.
Going forward the capabilities of the service can change, including potentially breaking changes such as
removing specific operations. This will allow for improvements to be applied on an on-going basis. This topic
will record any version specific differences and any limitations where the Web API hasn't yet achieved parity
with the organization service.

NOTE
While the v9.x releases can support specific differences, there have been no breaking changes added to v9.0, v9.1, or v9.2
releases. Each of these releases are have identical Web API behaviors.
Differences in API behavior is driven more by the solutions installed in the system rather than version of the product.
However, if we need to make a fundamental change that is not backward compatible, it will be included in a new version
number.
Guidance : Use the version number that was current when your code was written. Do not automatically use a newer
version without looking for documented differences here and testing. Do not assume a newer version wll be fully
backward compatible.

Web API version specific differences


The differences below refer to changes in the v8.2 and v9.0 versions of the Web API.

Encoding for special characters in FetchXML query response


For v8.x versions, response of FetchXML queries containing link-entities and their attributes contains Unicode
special characters such that '.' becomes 'x002e' and '@' becomes 'x0040'. This encoding for special characters is
not present in response of FetchXML queries for v9.x release.
Same name for table and column
If the name of a table (entity) and one of its columns (attributes) is the same, then "1" gets appended to the
attribute name in v8.x instances. For example, if an entity new_zipcode has an attribute with name as
new_zipcode then, the attribute name will change to new_zipcode1 .
For v9.x instances, nothing gets appended to the attribute name.

New operations added


The following operations have been added to the Web API for the v9.x release.
O P ERAT IO N S O P ERAT IO N S ( C O N T 'D) O P ERAT IO N S ( C O N T 'D)

GrantAccessRequest ModifyAccessRequest RetrieveSharedPrincipalsAndAccessReq


uest

Web API Limitations


The Dataverse Web API provides complete parity with the capabilities of the Organization service. For Dataverse,
this topic describes the limitations carried forward from the Dataverse v8.x release. For earlier releases, see
Dynamics CRM 2016 Web API Limitations.

NOTE
If you defined a custom action which included a complex return value and a simple return value, a corresponding Action
was not available in the Web API but was available using the 2011 SOAP endpoint. A complex return value is an
EntityReference , Entity , or EntityCollection . You can have any combination of simple return values or a single
complex return value. More information: Create your own actions.

See also
Use the Dataverse Web API
Authenticate to Dataverse with the Web API
Web API types and operations
Perform operations using the Web API
Search across table data using Dataverse search
10/4/2021 • 10 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

Dataverse search delivers fast and comprehensive search results across multiple tables, in a single list, sorted by
relevance. Dataverse search must be enabled in your target environment by an administrator before you can
use the feature. More information: Using Dataverse search to search for records
To begin using Dataverse search, your application simply issues an HTTP POST request (presently Web API only)
to start a Dataverse search. When searching data, specify optional query parameters to set criteria for how the
environment data is to be searched.
There are three Dataverse search methods that can be used in the Power Apps web application UI:
Search : Provides a search results page.
Suggestions : Provides suggestions as the user enters text into a form field.
Autocomplete : Provides autocompletion of input as the user enters text into a form field.
The following sections describe how to access the above mentioned search capabilities from application code.

Search
The minimum syntax of a Dataverse search HTTP request is as shown below.

POST [Organization URI]/api/search/v1.0/query


{
“search”: “<search term>”
}

The search parameter value contains the term to be searched for and has a 100-character limit.
A successful search response returns an HTTP status of 200 and consists of:
value: a list of tables. By default, 50 results are returned. This also includes search highlights, which
indicate matches to the search parameter value contained within the crmhit tag of the response.
totalrecordcount: The total count of results (of type long). A value of −1 is returned if
returntotalrecordcount is set to false (default).

facets: The facet results.


In addition, you can add one or more query parameters to customize how the search is to be done and which
results are returned. The supported query parameters are indicated in the following section.
Query parameters
The following query parameters are supported for Dataverse search.
entities=[list<string>] (optional)

The default table list searches across all Dataverse search–configured tables and columns. The default list is
configured by your administrator when Dataverse search is enabled.
facets=[list<string>] (optional)

Facets support the ability to drill down into data results after they've been retrieved.

POST [Organization URI]/api/search/v1.0/query


{
“search”: ”maria”,

“facets”: ["@search.entityname,count:100",
"account.primarycontactid,count:100",
"ownerid,count:100",
"modifiedon,values:2019-04-27T00:00:00|2020-03-27T00:00:00|2020-04-20T00:00:00|2020-04-27T00:00:00",
"createdon,values:2019-04-27T00:00:00|2020-03-27T00:00:00|2020-04-20T00:00:00|2020-04-27T00:00:00"]
}

filter=[string] (optional)

Filters are applied while searching data and are specified in standard OData syntax.

POST [Organization URI]/api/search/v1.0/query


{
“search”: ”maria”,

“filter”: "account:modifiedon ge 2020-04-27T00:00:00,


activities: regardingobjecttypecode eq 'account', annotation:objecttypecode eq 'account',
incident: (prioritycode eq 1 or prioritycode eq 2)"
}

returntotalrecordcount = true | false (optional)

Specify true to return the total record count; otherwise false . The default is false .
skip=[int] (optional)

Specifies the number of search results to skip.


top=[int] (optional)

Specifies the number of search results to retrieve. The default is 50, and the maximum value is 100.
orderby=[list<string>] (optional)

A list of comma-separated clauses where each clause consists of a column name followed by 'asc' (ascending,
which is the default) or 'desc' (descending). This list specifies how to order the results in order of precedence. By
default, results are listed in descending order of relevance score (@search.score). For results with identical
scores, the ordering will be random.
For a set of results that contain multiple table types, the list of clauses for orderby must be globally applicable
(for example, modifiedon, createdon, @search.score). Note that specifying the orderby parameter overrides the
default. For example, to get results ranked (in order of precedence) by relevance, followed by the most recently
modified records listed higher:
“orderby”: [“@search.score desc", "modifiedon desc”]

If the query request includes a filter for a specific table type, orderby can optionally specify table-specific
columns.
searchmode= any | all (optional)

Specifies whether any or all the search terms must be matched to count the document as a match. The default is
'any'.
NOTE
The searchMode parameter on a query request controls whether a term with the NOT operator is AND'ed or OR'ed with
other terms in the query (assuming there is no + or | operator on the other terms).
Using searchMode=any increases the recall of queries by including more results, and by default will be interpreted as "OR
NOT". For example, "wifi -luxury" will match documents that either contain the term "wifi" or those that don't contain the
term "luxury".
Using searchMode=all increases the precision of queries by including fewer results, and by default will be interpreted as
"AND NOT". For example, "wifi -luxury" will match documents that contain the term "wifi" and don't contain the term
"luxury".

searchtype= simple | full (optional)

The search type specifies the syntax of a search query. Using 'simple' selects simple query syntax and 'full'
selects Lucene query syntax. The default is 'simple'.
The simple query syntax supports the following functionality:

F UN C T IO N A L IT Y DESC RIP T IO N

Boolean operators AND operator; denoted by +


OR operator; denoted by |
NOT operator; denoted by -

Precedence operators A search term "hotel+(wifi | luxury)" will search for results
containing the term "hotel" and either "wifi" or "luxury" (or
both).

Wildcards Trailing wildcard are supported. For example, "Alp*" searches


for "alpine".

Exact matches A query enclosed in quotation marks " ".

The Lucene query syntax supports the following functionality:

F UN C T IO N A L IT Y DESC RIP T IO N

Boolean operators Provides an expanded set compared to simple query syntax.


AND operator; denoted by AND, &&, +
OR operator; denoted by OR, ||
NOT operator; denoted by NOT, !, –

Precedence operators The same functionality as simple query syntax.

Wildcards In addition to a trailing wildcard, also supports a leading


wildcard.
Trailing wildcard – "alp*"
Leading wildcard - “/.*pine/”

Fuzzy search Supports queries misspelled by up to two characters.


"Uniersty~" will return "University"
"Blue~1" will return "glue", "blues"
F UN C T IO N A L IT Y DESC RIP T IO N

Term boosting Weighs specific terms in a query differently.


"Rock^2 electronic" will return results where the matches to
"rock" are more important than matches to "electronic".

Proximity search Returns results where terms are within x words of each other,
for more contextual results.
For example, "airport hotel"~5 returns results where
"airport" and "hotel" are within five words of each other, thus
boosting the chances of finding a hotel located close to an
airport.

Regular expression (regex) search For example, /[mh]otel/ matches "motel" or "hotel".

NOTE
Wildcards are used only for word completion in Dataverse search. As a rule, querying with a leading wildcard will take
significantly longer than not using a wildcard, so we encourage you to explore alternative ways to find what you're looking
for and only use leading wildcards sparingly, if at all.

In order to use any of the search operators as part of the search text, escape the character by prefixing it with a
single backslash (\). Special characters that require escaping include the following: + - & | ! ( ) { } [ ] ^ " ~ * ? : \ /
Example: basic search
Below is an example of a basic search request and response.
Request

POST [Organization URI]/api/search/v1.0/query


{
“search”: ”maria”,

“facets”: ["@search.entityname,count:100",
"account.primarycontactid,count:100",
"ownerid,count:100",
"modifiedon,values:2019-04-27T00:00:00|2020-03-27T00:00:00|2020-04-20T00:00:00|2020-04-27T00:00:00",
"createdon,values:2019-04-27T00:00:00|2020-03-27T00:00:00|2020-04-20T00:00:00|2020-04-27T00:00:00"]
}

Response

{
"value": [
{
"@search.score": 0.4547767,
"@search.highlights": {
"emailaddress1": [
"{crmhit}maria{/crmhit}@contoso.com"
],
"firstname": [
"{crmhit}Maria{/crmhit}"
],
"fullname": [
"{crmhit}Maria{/crmhit} Sullivan"
]
},
"@search.entityname": "contact",
"@search.objectid": "16ffc791-d06d-4d8c-84ad-89a8978e14f3",
"ownerid": "bb2500d1-5e6d-4953-8389-bfedf57e3857",
"ownerid": "bb2500d1-5e6d-4953-8389-bfedf57e3857",
"owneridname": "Corey Gray",
"@search.ownerid.logicalname": "systemuser",
"@search.objecttypecode": 2,
"fullname": "Maria Sullivan",
"entityimage_url": **null**,
"createdon": "10/9/2020 5:27 PM",
"modifiedon": "10/9/2020 5:27 PM",
"emailaddress1": "[email protected]",
"address1_city": **“Seattle”**,
"address1_telephone1": **“206-400-0200”**,
"parentcustomerid": **null**,
"parentcustomeridname": **null**,
"telephone1": **“206-400-0300”**
}
],
"facets": {
"account.primarycontactid": [],
"ownerid": [
{
"Type": "Value",
"Value": "31ca7d4b-701c-4ea9-8714-a89a5172106e",
"OptionalValue": "Corey Gray",
"Count": 1
}
],
"@search.entityname": [
{
"Type": "Value",
"Value": "contact",
"Count": 1
}
],
"modifiedon": [
{
"Type": "Range",
"To": "4/27/2019 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "4/27/2019 12:00 AM",
"To": "3/27/2020 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "3/27/2020 12:00 AM",
"To": "4/20/2020 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "4/20/2020 12:00 AM",
"To": "4/27/2020 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "4/27/2020 12:00 AM",
"Count": 1
}
],
"createdon": [
{
"Type": "Range",
"To": "4/27/2019 12:00 AM",
"Count": 0
},
{
{
"Type": "Range",
"From": "4/27/2019 12:00 AM",
"To": "3/27/2020 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "3/27/2020 12:00 AM",
"To": "4/20/2020 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "4/20/2020 12:00 AM",
"To": "4/27/2020 12:00 AM",
"Count": 0
},
{
"Type": "Range",
"From": "4/27/2020 12:00 AM",
"Count": 1
}
]
},
"totalrecordcount": -1
}

Suggestions
Suggestions provide a list of matches to the specified search parameter value, based on a table record's primary
column. This is different from a regular search request because a suggestion search only searches through a
record's primary column, while search requests search through all Dataverse search–enabled table columns.
The minimum syntax of a suggestion search HTTP request is as shown below.

POST [Organization URI]/api/search/v1.0/suggest


{
“search”: “<text-fragment>”
}

The search parameter value provides a text string for the search to match and has a three-character minimum
length.
A successful search response returns an HTTP status of 200 and contains "value", which is a list consisting of text
or a document where the text is the suggestion with highlights, and the document is a dictionary
<string,object> of the suggestion result. By default, five results are returned. Suggestion highlights indicate
matches to the search parameter value and are contained within the crmhit tag in the response.
In addition, you can add one or more query parameters to customize how the suggestion search is to be done
and which results are returned. The supported query parameters are indicated in the following section.
Query parameters
usefuzzy=true | false (optional)

Use fuzzy search to aid with misspellings. The default is false .


top=[int] (optional)

Number of suggestions to retrieve. The default is 5.


orderby=[List<string>] (optional)

A list of comma-separated clauses where each clause consists of an column name followed by 'asc' (ascending)
or 'desc' (descending). This list specifies how to order the results in order of precedence. By default, results are
listed in descending order of relevance score (@search.score). For results with identical scores, the ordering will
be random.
For a set of results that contain multiple table types, the list of clauses for orderby must be globally applicable
(for example, modifiedon, createdon, @search.score). Note that specifying the orderby parameter overrides the
default. For example, to get results ranked (in order of precedence) by relevance, followed by the most recently
modified records listed higher:
“orderby”: [“@search.score desc", "modifiedon desc”]

If the query request includes a filter for a specific table type, orderby can optionally specify table-specific
columns.
entities=[list<string>] (optional)

The default is searching across all Dataverse search–configured tables.


filter=[string] (optional)

Filters are applied while searching data and are specified in standard OData syntax.
Request

POST [Organization URI]/api/search/v1.0/suggest


{
“search”: ”mar”,

“filter”: "account:modifiedon ge 2020-04-27T00:00:00,


activities:regardingobjecttypecode eq 'account', annotation:objecttypecode eq 'account'"
}

Example: suggestion search


The following is an example of a basic suggestion search request.
Request

POST [Organization URI]/api/search/v1.0/suggest


{
“search”: ”mar”
}

Response
{
"value": [
{
"text": "{crmhit}Mar{/crmhit}ia Sullivan",
"document": {
"@search.objectid": "52a33850-8f0a-eb11-a813-000d3a8ab142",
"@search.entityname": "contact",
"@search.objecttypecode": 2,
"fullname": "Maria Sullivan",
"entityimage_url": **null**,
"emailaddress1": "[email protected]",
"address1_city": **null**,
"address1_telephone1": **null**,
"parentcustomerid": **null**,
"parentcustomeridname": **null**,
"telephone1": **null**
}
}
]
}

Autocomplete
Provides autocompletion of user input. Autocomplete is based on a table record's primary column.
The minimum syntax of a Dataverse search HTTP request is as follows.

POST [Organization URI]/api/search/v1.0/autocomplete


{
“search”: ”<text-fragment>”
}

A successful search response returns an HTTP status of 200 and consists of "value", which is a string.
In addition, you can add one or more query parameters to customize how the search is to be done and which
results are returned. The supported query parameters are indicated in the following section.
Query parameters
usefuzzy=true | false (optional)

Fuzzy search to aid with misspellings. The default is false .


entities=[list<string>] (optional)

The default scope is searching across all Dataverse search–configured tables and columns.
filter=[string] (optional)

Filters are applied while searching data and are specified in standard OData syntax.
Request

POST [Organization URI]/api/search/v1.0/autocomplete


{
“search”: ”mar”,

“filter”: "account:modifiedon ge 2020-04-27T00:00:00,


activities:regardingobjecttypecode eq 'account', annotation:objecttypecode eq 'account'"
}

Example: autocomplete search


The following is an example of a basic autocomplete request.
Request

POST [Organization URI]/api/search/v1.0/autocomplete


{
“search”: ”mar”
}

Response

{
"value": "{crmhit}maria{/crmhit}"
}

See also
Configure Dataverse search to improve search results and performance
Compare search options in Microsoft Dataverse
Retrieve related table records with a query
Query Data using the Web API
Connect with your Dataverse environment
Discover the URL for your organization
5/21/2021 • 3 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

You can create a client for a specific Microsoft Dataverse environment and not provide the user any options
about which environment they can connect with. Only valid users for that specific environment can use your
client.
If you want people using your client to be able to connect to any Dataverse environment they have access to you
could prompt them to enter the URL for their environment, but this is not recommended. Each user may have
access to multiple Dataverse environments. Users may not know or remember the URL for their environment.
Expecting people to enter this URL is bound to frustrate users.
Instead, your client should provide a list of each of the available environments based on the user’s credentials. If
there is more than one environment available, your application should prompt the user to choose which
environment they want to connect with.
With Dataverse, server and organization allocation may change as part of datacenter management and load
balancing. Therefore, a discovery service provides a way to discover which server is serving an instance at a
given time.
When accessing the Discovery Service using the OData V4 RESTful API, you can add standard $filter and
$select parameters to the service request to customize the returned list of instance data.

IMPORTANT
Effective March 2, 2020, the regional Discovery Service will be deprecated. Applications must use the global Discovery
Service that is documented in this topic.
For Dynamics 365 US Government users, a global Discovery Service endpoint is available for the GCC and GCC High
users, and the URL is different from the regular global Discovery Service URL. More information: Dynamics 365
Government URLs.

Information provided by the Discovery Service


Organization information is stored in the Instance table of the Discovery Service. To see the kind of
information contained in that table, send an HTTP GET request to the service for one of your instances.

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances(UniqueName='myorg')

In the above example, the global Discovery Service is used to obtain the organization information of the
instance with a unique name of "myorg". More details about this request is provided later in this topic.
Scope of the returned information
For the global Discovery Service, the Instances entity set, returns the set of instances (environments) that the
user has access to across all geographies, when no filters are applied. The returned data has a scope as
described below.
Includes all instances in the commercial cloud where the user is provisioned and enabled, except sovereign
clouds instances are not returned
Does not include instances where the user's account is disabled
Does not include instances where users have been filtered out based on an instance security group
Does not include instances where the user has access as a result of being a delegated administrator
If the calling user has access to no instances, the response simply returns an empty list

How to access the Discovery Service


In general, the Web address of the Discovery Service has the following format:
<service base address>/api/discovery/ . You can easily find the Web address and version number for your
deployment in the Microsoft Dataverse Web application by navigating to Settings > Customization >
Developer Resources
Dataverse Discovery services
The service base address of the global Discovery Service is : https://globaldisco.crm.dynamics.com/ . This results
in the service address of https://globaldisco.crm.dynamics.com/api/discovery/ .

Using the Discovery service


An entity set named Instances is used to obtain instance information. You can use $select and $filter with
the Instances entity set to filter the returned data. You can also use $metadata to obtain the metadata document
of the service.
Authentication
Accessing the Discovery Service requires authentication with an OAuth access token.
When the Discovery Service is configured for OAuth authentication, a request sent to the service without an
access token triggers a bearer challenge with the authority of the “common” endpoint and the resource ID of the
service.
CORS support
The Discovery Service supports the CORS standard for cross-origin access. For more information about CORS
support see Use OAuth with Cross-Origin Resource Sharing to connect a Single Page Application.
Examples
Get the details of a specific instance. If you leave out the GUID, all instances that the authenticated user
has access to are returned.

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances(<guid>)

You can use the UniqueName column as an alternate key.

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances(UniqueName='myorg')

Retrieve a list of available instances, filtered by production type.

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances?
$select=DisplayName,Description&$filter=Type+eq+0

See also
Discovery Service sample (C#)
Modify your code to use global Discovery Service
4/30/2021 • 2 minutes to read • Edit Online

The Discovery Service APIs can be used by your application to discover business organization instances
(environments) that the application user has access to. If your application currently uses the Organization
Service API on the 2011 SOAP endpoint to discover organization instances, you can follow the steps in this topic
and convert your application to access organization details using the OData V4 RESTful API with the global
Discovery Service URL. If your application accesses the Discovery Service using the regional Discovery Service
URL, you will need to change the application code from using the regional URL to the global Discovery Service
URL.
A detailed description of using the Discovery Service with the RESTful API can be found on the Discover the URL
for your organization page.

IMPORTANT
When accessing the Discovery Service, it is strongly recommended that your application use the global Discovery Service
endpoint (https://globaldisco.crm.dynamics.com) and not the regional Discovery Service endpoint, which will be
deprecated on March 2, 2020. The global Discovery Service is only available when using the RESTful API.

The rest of this document describes the changes that may be needed to call the Discovery Service using the
RESTful API.

Authentication
Accessing the Discovery Service using the RESTful API requires authentication with an OAuth 2.0 access token. If
your application code uses WS-Trust SAML tokens for authentication, you need to change your application code
to acquire an OAuth 2.0 token from Azure Active Directory (AD), and then add that token in the Authorization
header of the Discovery Service API calls. More information: Use OAuth with Microsoft Dataverse.

OData API calls


The example HTTP requests shown below are supported by the Discovery Service RESTful API. These examples
use the Instances API to return the same organization data as the RetrieveOrganizationsRequest and
RetrieveOrganizationRequest message requests of the Organization Service API.
Get all instances for the user in all regions.

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances

Get all instances for the user in a specific region.

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances(Region={region})

Response
{
"value":[
{
"Id":"<GUID>",
"UniqueName":"myorg",
"UrlName":"orgurlname",
"FriendlyName":"My Org",
"State":0,
"Version":"<Version>",
"Url":"https://orgurlname.crm.dynamics.com",
"ApiUrl":"https://orgurlname.api.crm.dynamics.com"
}
]
}

Get a single instance by ID

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances(<guid>)

Get a single instance by unique name

GET https://globaldisco.crm.dynamics.com/api/discovery/v2.0/Instances(UniqueName='myorg')

Response

{
"Id":"<GUID>",
"UniqueName":"myorg",
"UrlName":"orgurlname",
"FriendlyName":"My Org",
"State":0,
"Version":"<Version>",
"Url":"https://orgurlname.crm.dynamics.com",
"ApiUrl":"https://orgurlname.api.crm.dynamics.com"
}

Mapping of columns
The table shown below shows the column mapping in the responses returned from the Discovery Service when
using the two APIs. These are applicable to all above example calls.

RESP O N SE F IEL D ( SO A P EN DP O IN T ) RESP O N SE F IEL D ( O DATA V4 REST F UL EN DP O IN T )

Endpoints[WebApplication] Url

Endpoints[OrganizationService] {ApiUrl}/XRMServices/2011/Organization.svc

Endpoints[OrganizationDataService] {ApiUrl}//XRMServices/2011/OrganizationData.svc

FriendlyName FriendlyName

OrganizationId Id

OrganizationVersion Version
RESP O N SE F IEL D ( SO A P EN DP O IN T ) RESP O N SE F IEL D ( O DATA V4 REST F UL EN DP O IN T )

State State
0: Enabled
1: Disabled

UniqueName UniqueName

UrlName UrlName

Deprecated API call


The Organization Service API message GetUserIdByExternalId is not supported in the RESTful API.

See Also
Discovery Services
Use the Dataverse Web API
Global Discovery Service Sample (C#)
7/19/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample shows how to access the global Discovery Service using the Web API.

How to run this sample


The sample source code is available on Github at PowerApps-Samples/cds/webapi/C#/GlobalDiscovery.
To run the sample:
1. Download or clone the sample so that you have a local copy.
2. Open the project solution file in Visual Studio.
3. Press F5 to build and run the sample.

What this sample does


When run, the sample opens a logon form where you must specify the Microsoft Dataverse credentials for an
enabled user. The program then displays to the console window a list of Dataverse environment instances that
the specified user is a member of.
Other important aspects of this sample include:
1. Handles breaking API changes in different versions of Azure Active Directory Authentication Library (ADAL)
2. No dependency on helper code or a helper library since all required code, including authentication code, is
provided.

How this sample works


This sample uses an HttpClient to authenticate the specified user using ADAL, and then calls the global
Discovery Service to return information about available Dataverse environment instances the user is a member
of.
Demonstrates
The sample depends on the GetInstances method and the Instance class below:
/// <summary>
/// Uses the global discovery service to return environment instances
/// </summary>
/// <param name="username">The user name</param>
/// <param name="password">The password</param>
/// <returns>A list of Instances</returns>
static List<Instance> GetInstances(string username, string password)
{

string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";


HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GetAccessToken(username, password,
new Uri("https://disco.crm.dynamics.com/api/discovery/")));
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri(GlobalDiscoUrl);

HttpResponseMessage response =
client.GetAsync("api/discovery/v2.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;

if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
string result = response.Content.ReadAsStringAsync().Result;
JObject body = JObject.Parse(result);
JArray values = (JArray)body.GetValue("value");

if (!values.HasValues)
{
return new List<Instance>();
}

return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
}
else
{
throw new Exception(response.ReasonPhrase);
}
}

/// <summary>
/// Object returned from the Discovery Service.
/// </summary>
class Instance
{
public string Id { get; set; }
public string UniqueName { get; set; }
public string UrlName { get; set; }
public string FriendlyName { get; set; }
public int State { get; set; }
public string Version { get; set; }
public string Url { get; set; }
public string ApiUrl { get; set; }
public DateTime LastUpdated { get; set; }
}

See Also
Discover the URL for your organization
Web API data operations samples
6/18/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

You can use the Microsoft Dataverse Web API with a wide variety of programming languages or libraries. This
guide provides a number of code samples demonstrating how to use the Web API in different ways. This topic
introduces the samples available for each group of operations and how to perform these operations using
different languages or libraries.

Web API samples list


The following table describes the Dataverse Web API samples and their language-specific implementations.

C L IEN T - SIDE JAVA SC RIP T


L A N GUA GE- N EUT RA L DESC RIP T IO N C # IM P L EM EN TAT IO N IM P L EM EN TAT IO N

Web API Samples (this topic) Web API Samples (C#) Web API Samples (Client-side
JavaScript)

Groups of operations
The following table classifies the samples by demonstrated operation groups.

GRO UP DESC RIP T IO N

Web API Basic Operations Sample How to perform basic CRUD (Create, Retrieve, Update, and
Delete) and associative operations.
More information:
- Create a table row using the Web API
- Retrieve a table row using the Web API
- Update and delete table rows using the Web API
- Associate and disassociate table rows using the Web
API

Web API Query Data Sample How to perform basic query requests.
More information:
- Query Data using the Web API
- Retrieve and execute predefined queries

Web API Conditional Operations Sample How to perform certain categories of operations that are
conditionally based upon the version of the table row
contained on the server and/or currently maintained by the
client.
More information:
- Perform conditional operations using the Web API
GRO UP DESC RIP T IO N

Web API Functions and Actions Sample How to use bound/unbound functions and actions, including
custom actions.
More information:
- Use Web API functions
- Use Web API actions

Language or library
The following table lists the topics that cover the common language- or library-specific implementation issues.

L A N GUA GE O R L IB RA RY DESC RIP T IO N

Web API Samples (C#) Describes the common elements used in this group of C#
samples which demonstrate operations using basic .NET
classes and a minimum of helper libraries.

Web API Samples (Client-side JavaScript) Under construction.

See also
Use the Dataverse Web API
Web API Basic Operations Sample
Web API Query Data Sample
Web API Conditional Operations Sample
Web API Functions and Actions Sample
Web API Samples (C#)
Web API Basic Operations Sample
6/18/2021 • 11 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This collection of sample code snippets demonstrate how to perform basic CRUD (Create, Retrieve, Update, and
Delete) and associative operations using the Microsoft Dataverse Web API.
Web API Basic Operations Sample (C#)
This topic describes a common set of operations implemented by each sample snippet in this group. This topic
describes the HTTP requests and responses and text output that each sample will perform without the language
specific details. See the language specific descriptions and the individual samples for details about how these
operations are performed.

Demonstrates
This sample is divided into the following sections, containing Dataverse Web API operations which are discussed
in greater detail in the specified associated conceptual topics.

C O DE SEC T IO N A SSO C IAT ED C O N C EP T UA L TO P IC S

Section 1: Basic create and update operations Basic create


Create with data returned
Basic update
Update with data returned

Section 2: Create with association Associate table rows on create

Section 3: Create related table rows (deep insert) Create related table rows in one operation

Section 4: Associate and disassociate existing table rows Associate and disassociate table rows using the Web API

Section 5: Delete table rows (sample cleanup) Basic delete

NOTE
For brevity, less pertinent HTTP headers have been omitted. The URLs of the records will vary with the base organization
address and the ID of the row assigned by your Dataverse server.

Section 1: Basic create and update operations


This section creates a single contact then performs a series of updates upon that instance. Note that the
response header OData-EntityId contains the URL to this newly created row, which parenthetically includes the
unique ID for this record.
1. Create a new contact, named Peter Cambel.
Request

POST https://[Organization URI]/api/data/v9.0/contacts HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"firstname": "Peter",
"lastname": "Cambel"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03)

Console output

Contact 'Peter Cambel' created.

The properties available for each type are defined within the metadata document and are also documented for
each type in the Web API EntityType Reference section. For more general information, see Web API types and
operations.
2. Update the contact with values for annual income ($80,000) and job title (Junior Developer).
Request

PATCH https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"annualincome": 80000,
"jobtitle": "Junior Developer"
}

Response

HTTP/1.1 204 No Content

Console output

Contact 'Peter Cambel' updated with job title and annual income.

3. Retrieve the contact with its set of explicitly initialized properties. The fullname is a read-only property
that is calculated from the firstname and lastname properties, which were explicitly initialized when the
instance was created. In contrast, the description property was not explicitly initialized, so it retains its
default value, a null string.
Note that the response, in addition to the requested values and typical headers, also automatically returns
the following types of additional information:
The primary ID for the current table type, here contactid .
An ETag value, denoted by the @odata.etag key, which identifies the specific version of the
resource requested. For more information, see Perform conditional operations using the Web API.
The metadata context, denoted by the @odata.context key, provides a way to compare query
results to determine if they came from the same query.
A _transactioncurrencyid_value that indicates the local currency of the monetary transaction.

Request

GET https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03)?


$select=fullname,annualincome,jobtitle,description HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,annualincome,jobtitle,description)/$entity",
"@odata.etag":"W/\"628883\"",
"fullname":"Peter Cambel",
"annualincome":80000.0000,
"jobtitle":"Junior Developer",
"description":null,
"_transactioncurrencyid_value":"0d4ed62e-95f7-e511-80d1-00155da84c03",
"contactid":"60f77a42-5f0e-e611-80e0-00155da84c03"
}

Console output

Contact 'Peter Cambel' retrieved:


Income: 80000
Job title: Junior Developer
Description: .

IMPORTANT
You should always use selection and filtering in retrieval operations to optimize performance. For more information, see
Query Data using the Web API.

4. Update the contact instance by supplying new values to these same properties.
Request
PATCH https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"jobtitle": "Senior Developer",
"annualincome": 95000,
"description": "Assignment to-be-determined"
}

Response

HTTP/1.1 204 No Content

Console output

Contact 'Peter Cambel' updated:


Job title: Senior Developer
Annual income: 95000
Description: Assignment to-be-determined

IMPORTANT
Only send changed properties in update requests. For more information, see Basic update.

5. Explicitly set a single property, the primary phone number. Note this is a PUT request and that the JSON key
named value is used when performing operations on individual properties.
Request

PUT https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03)/telephone1


HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"value": "555-0105"
}

Response

HTTP/1.1 204 No Content

Console output

Contact 'Peter Cambel' phone number updated.

6. Retrieve that same single property, the primary phone number. Again note the use of the key named value .

Request
GET https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03)/telephone1
HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context":"https://[Organization URI]/api/data/v9.0/$metadata#contacts(60f77a42-5f0e-e611-80e0-
00155da84c03)/telephone1",
"value":"555-0105"
}

Console output

Contact's telephone# is: 555-0105.

7. Create a similar contact but also return instance information in the same operation. This latter capability is
enabled by the Prefer: return=representation header.

Request

POST https://[Organization URI]/api/data/v9.0/contacts?$select=fullname,annualincome,jobtitle,contactid


HTTP/1.1
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: return=representation
{
"firstname": "Peter_Alt",
"lastname": "Cambel",
"jobtitle": "Junior Developer",
"annualincome": 80000,
"telephone1": "555-0110"
}

Response

HTTP/1.1 201 Created


Content-Type: application/json; odata.metadata=minimal
Preference-Applied: return=representation
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts/$entity","@odata.etag":"W/\"758870\"","_transactioncurrencyid_value":"
0d4ed62e-95f7-e511-80d1-00155da84c03","annualincome":80000.0000,"contactid":"199250b7-6cbe-e611-80f7-
00155da84c08","jobtitle":"Junior Developer","fullname":"Peter_Alt Cambel"
}

Console output
Contact 'Peter_Alt Cambel' created:
Annual income: 80000
Job title: Junior Developer
Contact URI: https://[Organization URI]/api/data/v9.0/contacts(199250b7-6cbe-e611-80f7-00155da84c08)

8. Update this similar contact and also return instance information in the same operation. Again, this capability
is enabled by the Prefer: return=representation header.
Request

POST https://[Organization URI]/api/data/v9.0/contacts?$select=fullname,annualincome,jobtitle,contactid


HTTP/1.1
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: return=representation
{
"firstname": "Peter_Alt",
"lastname": "Cambel",
"jobtitle": "Junior Developer",
"annualincome": 80000,
"telephone1": "555-0110"
}

Response

HTTP/1.1 201 Created


Content-Type: application/json; odata.metadata=minimal
Preference-Applied: return=representation
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts/$entity","@odata.etag":"W/\"758870\"","_transactioncurrencyid_value":"
0d4ed62e-95f7-e511-80d1-00155da84c03","annualincome":80000.0000,"contactid":"199250b7-6cbe-e611-80f7-
00155da84c08","jobtitle":"Junior Developer","fullname":"Peter_Alt Cambel"
}

Console output

Contact 'Peter_Alt Cambel' updated:


Annual income: 95000
Job title: Senior Developer

Section 2: Create with association


This section creates a new account instance named Contoso, Ltd. and associates it to an existing contact,
Peter Cambel , which was created in Section 1. This creation and association is performed in a single POST
operation.
1. Create the Contoso, Ltd. account and set its primary contact attribute to the existing contact Peter Cambel.
The @odata.bind annotation indicates that an association is being created, here binding the
primarycontactid single-valued navigation property to an existing contact, Peter Cambel.

Request
POST https://[Organization URI]/api/data/v9.0/accounts HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"name": "Contoso Inc",
"telephone1": "555-5555",
"[email protected]": "https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-
00155da84c03)"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: https://[Organization URI]/api/data/v9.0/accounts(65f77a42-5f0e-e611-80e0-00155da84c03)

Console output

Account 'Contoso Inc' created.

2. Retrieve the primary contact for the account Contoso, Ltd., again using $expand with the primarycontactid
single-valued navigation property to access the associated contact EntityType record.
Request

GET https://[Organization URI]/api/data/v9.0/accounts(65f77a42-5f0e-e611-80e0-00155da84c03)?


$select=name,&$expand=primarycontactid($select=fullname,jobtitle,annualincome) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,primarycontactid,primarycontactid(fullname,jobtitle,annualincome)
)/$entity",
"@odata.etag":"W/\"628886\"",
"name":"Contoso Inc",
"accountid":"65f77a42-5f0e-e611-80e0-00155da84c03",
"primarycontactid":{
"@odata.etag":"W/\"628885\"",
"fullname":"Peter Cambel",
"jobtitle":"Senior Developer",
"annualincome":95000.0000,
"_transactioncurrencyid_value":"0d4ed62e-95f7-e511-80d1-00155da84c03",
"contactid":"60f77a42-5f0e-e611-80e0-00155da84c03"
}
}

Console output
Account 'Contoso Inc' has primary contact 'Peter Cambel':
Job title: Senior Developer
Income: 95000

Section 3: Create related table rows (deep insert)


This section demonstrates how to create a table row and related row, in a single POST request. Using this
method, all rows are newly created; there are no existing rows to associate with. This approach has two
advantages. It is more efficient, replacing multiple simpler creation and association operations with one
combined operation. Also, it is atomic, where either the entire operation succeeds and all the related objects are
created, or the operation fails and none are created.
This section creates an account, its primary contact, and a set of tasks for that contact in one request.
1. Create the account Fourth Coffee and its primary contact Susie Curtis and her three related tasks in one
operation. Note the use of the single-valued primarycontactid navigation property and the collection-valued
navigation property Contact_Tasks to define these relationships, respectively. Single-valued navigational
properties take an object value, whereas collection-valued navigation properties take an array value.
Request

POST https://[Organization URI]/api/data/v9.0/accounts HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"name": "Fourth Coffee",
"primarycontactid": {
"firstname": "Susie",
"lastname": "Curtis",
"jobtitle": "Coffee Master",
"annualincome": 48000,
"Contact_Tasks": [
{
"subject": "Sign invoice",
"description": "Invoice #12321",
"scheduledend": "2016-04-19T00:00:00-07:00"
},
{
"subject": "Setup new display",
"description": "Theme is - Spring is in the air",
"scheduledstart": "2016-04-20T00:00:00-07:00"
},
{
"subject": "Conduct training",
"description": "Train team on making our new blended coffee",
"scheduledstart": "2016-06-01T00:00:00-07:00"
}
]
}
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: https://[Organization URI]/api/data/v9.0/accounts(6af77a42-5f0e-e611-80e0-00155da84c03)

Console output
Account 'Fourth Coffee' created.

2. Selectively retrieve the newly created Fourth Coffee account and its primary contact. An expansion is
performed on the single-valued navigation property primarycontactid .

Request

GET https://[Organization URI]/api/data/v9.0/accounts(6af77a42-5f0e-e611-80e0-00155da84c03)?


$select=name,&$expand=primarycontactid($select=fullname,jobtitle,annualincome) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,primarycontactid,primarycontactid(fullname,jobtitle,annualincome)
)/$entity",
"@odata.etag":"W/\"628902\"",
"name":"Fourth Coffee",
"accountid":"6af77a42-5f0e-e611-80e0-00155da84c03",
"primarycontactid":{
"@odata.etag":"W/\"628892\"",
"fullname":"Susie Curtis",
"jobtitle":"Coffee Master",
"annualincome":48000.0000,
"_transactioncurrencyid_value":"0d4ed62e-95f7-e511-80d1-00155da84c03",
"contactid":"6bf77a42-5f0e-e611-80e0-00155da84c03"
}
}

Console output

Account 'Fourth Coffee' has primary contact 'Susie Curtis':


Job title: Coffee Master
Income: 48000

3. Selectively retrieve the tasks associated with the primary contact retrieved in the previous operation. An
expansion is performed on the collection-valued navigation property Contact_Tasks .

Request

GET https://[Organization URI]/api/data/v9.0/contacts(6bf77a42-5f0e-e611-80e0-00155da84c03)?


$select=fullname,&$expand=Contact_Tasks($select=subject,description,scheduledstart,scheduledend) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,Contact_Tasks,Contact_Tasks(subject,description,scheduledstar
t,scheduledend))/$entity",
"@odata.etag":"W/\"628892\"",
"fullname":"Susie Curtis",
"contactid":"6bf77a42-5f0e-e611-80e0-00155da84c03",
"Contact_Tasks":[
{
"@odata.etag":"W/\"628903\"",
"subject":"Sign invoice",
"description":"Invoice #12321",
"scheduledstart":"2016-04-19T00:00:00Z",
"scheduledend":"2016-04-19T00:00:00Z",
"activityid":"6cf77a42-5f0e-e611-80e0-00155da84c03"
},
{
"@odata.etag":"W/\"628905\"",
"subject":"Setup new display",
"description":"Theme is - Spring is in the air",
"scheduledstart":"2016-04-20T00:00:00Z",
"scheduledend":"2016-04-20T00:00:00Z",
"activityid":"6df77a42-5f0e-e611-80e0-00155da84c03"
},
{
"@odata.etag":"W/\"628907\"",
"subject":"Conduct training",
"description":"Train team on making our new blended coffee",
"scheduledstart":"2016-06-01T00:00:00Z",
"scheduledend":"2016-06-01T00:00:00Z",
"activityid":"6ef77a42-5f0e-e611-80e0-00155da84c03"
}
]
}

Console output

Contact 'Susie Curtis' has the following assigned tasks:


Subject: Sign invoice,
Description: Invoice #12321
Start: 4/19/2016
End: 4/19/2016

Subject: Setup new display,


Description: Theme is - Spring is in the air
Start: 4/20/2016
End: 4/20/2016

Subject: Conduct training


Description: Train team on making our new blended coffee,
Start: 6/1/2016
End: 6/1/2016

Section 4: Associate and disassociate existing entities


This section demonstrates how to associate and disassociate existing table rows. Forming an association
requires the use of a reference URI and relationship object, which are then sent in a POST request.
Disassociating requires sending a DELETE request to the reference URI for that association. First a one-to-many
association is formed between a contact and an account. Then a many-to-many association is formed between a
competitor and one or more opportunities.
1. Add Peter Cambel as a contact to the account Fourth Coffee using the contact_customer_accounts collection-
valued navigation property. Note the use of the special key @odata.id to specify the associated record.

Request

POST https://[Organization URI]/api/data/v9.0/accounts(6af77a42-5f0e-e611-80e0-


00155da84c03)/contact_customer_accounts/$ref HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"@odata.id": "https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03)"
}

Response

HTTP/1.1 204 No Content

Console output

Contact 'Peter Cambel' associated to account 'Fourth Coffee'.

2. Confirm the previous operation by retrieving the collection of contacts for the account Fourth Coffee. The
response contains the array with a single element, the recently assigned contact Peter Cambel.
Request

GET https://[Organization URI]/api/data/v9.0/accounts(6af77a42-5f0e-e611-80e0-


00155da84c03)/contact_customer_accounts?$select=fullname,jobtitle HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle)","value":[
{
"@odata.etag":"W/\"632481\"","fullname":"Peter Cambel","jobtitle":"Senior
Developer","contactid":"00b6e0e2-b010-e611-80e1-00155da84c03"
}
]
}

Console output

Contact list for account 'Fourth Coffee':


Name: Peter Cambel, Job title: Senior Developer

3. Remove the association that was just created between account Fourth Coffee and contact Peter Cambel.
Request

DELETE https://[Organization URI]/api/data/v9.0/accounts(6af77a42-5f0e-e611-80e0-


00155da84c03)/contact_customer_accounts/$ref?$id=https://[Organization URI]/api/data/v9.0/contacts(60f77a42-
5f0e-e611-80e0-00155da84c03) HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 204 No Content

Console output

Contact 'Peter Cambel' dissociated from account 'Fourth Coffee'.

4. Create a competitor named Adventure Works .

Request

POST https://[Organization URI]/api/data/v9.0/competitors HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"name": "Adventure Works",
"strengths": "Strong promoter of private tours for multi-day outdoor adventures"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0
OData-EntityId: https://[Organization URI]/api/data/v9.0/accounts(77f77a42-5f0e-e611-80e0-00155da84c03)

Console output

Competitor 'Adventure Works' created.

5. Create an opportunity named River rafting adventure .


Request

POST https://[Organization URI]/api/data/v9.0/opportunities HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"name": "River rafting adventure",
"description": "Sales team on a river-rafting offsite and team building"
}

Response
HTTP/1.1 204 No Content
OData-Version: 4.0
OData-EntityId: https://[Organization URI]/api/data/v9.0/opportunities(7cf77a42-5f0e-e611-80e0-00155da84c03)

Console output

Opportunity 'River rafting adventure' created.

6. Associate this new opportunity to this new competitor. Note that the same general syntax is used in this
many-to-many association as was used in the previous one-to-many association.
Request

POST https://[Organization URI]/api/data/v9.0/opportunities(7cf77a42-5f0e-e611-80e0-


00155da84c03)/opportunitycompetitors_association/$ref HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
{
"@odata.id": "https://[Organization URI]/api/data/v9.0/competitors(77f77a42-5f0e-e611-80e0-00155da84c03)"
}

Response

HTTP/1.1 204 No Content

Console output

Opportunity 'River rafting adventure' associated with competitor 'Adventure Works'.

7. Selectively retrieve all the opportunities associated with the competitor Adventure Works. An array is
returned containing a single opportunity.
Request

GET https://[Organization URI]/api/data/v9.0/competitors(77f77a42-5f0e-e611-80e0-00155da84c03)?


$select=name,&$expand=opportunitycompetitors_association($select=name,description) HTTP/1.1
Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response
HTTP/1.1 200 OK
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#competitors(name,opportunitycompetitors_association,opportunitycompetitors_asso
ciation(name,description))/$entity",
"@odata.etag":"W/\"628913\"",
"name":"Adventure Works",
"competitorid":"77f77a42-5f0e-e611-80e0-00155da84c03",
"opportunitycompetitors_association":[
{
"@odata.etag":"W/\"628917\"",
"name":"River rafting adventure",
"description":"Sales team on a river-rafting offsite and team building",
"opportunityid":"7cf77a42-5f0e-e611-80e0-00155da84c03"
}
]
}

Console output

Competitor 'Adventure Works' has the following opportunities:


Name: River rafting adventure,
Description: Sales team on a river-rafting offsite and team building

8. Dissociate the opportunity from the competitor. Note again, that this has the same general syntax used to
remove a one-to-many association.
Request

DELETE https://[Organization URI]/api/data/v9.0/opportunities(7cf77a42-5f0e-e611-80e0-


00155da84c03)/opportunitycompetitors_association/$ref?$id=https://[Token-CRM-Org-
Name]/Contoso/api/data/v8.1/competitors(77f77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0

Response

HTTP/1.1 204 No Content

Console output

Opportunity 'River rafting adventure' disassociated from competitor 'Adventure Works'.

Section 5: Delete table rows


1. Each element of the collection of row URLs is deleted. The first is a contact record for Peter Cambel.
Request

DELETE https://[Organization URI]/api/data/v9.0/contacts(60f77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Response

HTTP/1.1 204 No Content

2. Subsequent iterations through the collection delete the remaining records.


Request

DELETE https://[Organization URI]/api/data/v9.0/accounts(65f77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


. . .

DELETE https://[Organization URI]/api/data/v9.0/accounts(6af77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


. . .

DELETE https://[Organization URI]/api/data/v9.0/contacts(6bf77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


. . .

DELETE https://[Organization URI]/api/data/v9.0/competitors(77f77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


. . .

DELETE https://[Organization URI]/api/data/v9.0/opportunities(7cf77a42-5f0e-e611-80e0-00155da84c03) HTTP/1.1


. . .

See also
Use the Dataverse Web API
Create a table row using the Web API
Retrieve a table row using the Web API
Update and delete table rows using the Web API
[Associate and disassociate table rows using the Web API](associate-disassociate-table rows-using-web-api.md)
Web API Basic Operations Sample (C#)
Web API Query Data Sample
6/18/2021 • 29 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This group of samples demonstrate how to query data using the Microsoft Dataverse Web API. This sample is
implemented as a separate project for the following languages:
Web API Query Data Sample (Client-side JavaScript)
Query Data Sample (C#)
This topic describes a common set of operations implemented by each sample in this group. This topic describes
the HTTP requests and responses and text output that each sample in this group will perform without the
language specific details. See the language specific descriptions and the individual samples for details about
how this operations are performed.

Demonstrates
This sample is divided into the following principal sections, containing Web API query data operations which are
discussed in greater detail in the associated conceptual topics.

TO P IC SEC T IO N A SSO C IAT ED TO P IC ( S)

Selecting specific properties Retrieve specific properties

Include formatted values

Using query functions Filter results

Standard query functions

Compose a query with functions

Web API Query Function Reference

Using operators Standard filter operators

Setting precedence Standard filter operators

Ordering results Order results

Filter results

Parameter alias Use parameter aliases with system query options

Limit results Limit results

Limits on number of rows returned


TO P IC SEC T IO N A SSO C IAT ED TO P IC ( S)

Expanding results Retrieve related rows by expanding navigation properties

Predefined queries Retrieve and execute predefined queries

userquery EntityType/

savedquery EntityType/

The following sections contain a brief discussion of the Dataverse Web API operations performed, along with the
corresponding HTTP messages and associated console output.

Sample data
To ensure the queries in this sample work properly, a standard set of sample rows is created by this sample.
These sample rows will be deleted unless the user chooses to not delete them. This is the data the sample will be
querying. You may get different results depending on any existing data in your environment.
The data is added using deep insert in a single POST request and matches the following structure:

{
"name": "Contoso, Ltd. (sample)",
"primarycontactid": {
"firstname": "Yvonne", "lastname": "McKay (sample)", "jobtitle": "Coffee Master",
"annualincome": 45000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
"Account_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
],
"contact_customer_accounts": [
{
"firstname": "Susanna", "lastname": "Stubberod (sample)", "jobtitle": "Senior Purchaser",
"annualincome": 52000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Nancy", "lastname": "Anderson (sample)", "jobtitle": "Activities Manager",
"annualincome": 55500, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Maria", "lastname": "Cambell (sample)", "jobtitle": "Accounts Manager",
"annualincome": 31000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
{
"firstname": "Nancy", "lastname": "Anderson (sample)", "jobtitle": "Logistics Specialist",
"annualincome": 63500, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Scott", "lastname": "Konersmann (sample)", "jobtitle": "Accounts Manager",
"annualincome": 38000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Robert", "lastname": "Lyon (sample)", "jobtitle": "Senior Technician",
"annualincome": 78000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Paul", "lastname": "Cannon (sample)", "jobtitle": "Ski Instructor",
"annualincome": 68500, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Rene", "lastname": "Valdes (sample)", "jobtitle": "Data Analyst III",
"annualincome": 86000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Jim", "lastname": "Glynn (sample)", "jobtitle": "Senior International Sales
Manager",
"annualincome": 81400, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
}
]
}

Selecting specific properties


Always construct queries using the $select query option, otherwise the server will return all properties of each
table row which reduces performance. This example demonstrates how to construct a basic query by selecting
three properties of a contact EntityType. The properties are fullname , jobtitle , annualincome . The section also
illustrates the differences between formatted and unformatted values as seen in the results of the contact's
annualincome property. More information:Request specific properties, Include formatted values.

In this example, we are requesting for a specific contact. In this case, it's the primary contact of the account,
Yvonne McKay (sample) .

HTTP Request
GET https://[Organization URI]/api/data/v9.0/contacts(b848fdee-c143-e611-80d5-00155da84802)?
$select=fullname,jobtitle,annualincome HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 517

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)/$entity",
"@odata.etag":"W/\"619718\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"15c364b2-bf43-e611-80d5-00155da84802"
}

Console output

Contact basic info:


Fullname: 'Yvonne McKay (sample)'
Jobtitle: 'Coffee Master'
Annualincome: '45000' (unformatted)
Annualincome: $45,000.00 (formatted)

Using query functions


Use filter options to set criteria for the results you want. You can build simple to complex filters using a
combination of query functions, comparison operators, and logical operators. More information:Filter results.
Query functions are functions that can be used as a filter criteria in a query. There are standard query functions
and Dataverse specific query functions. These functions accept parameters and return a Boolean value. This
sample illustrates how to create a query for each type.
Standard query functions
Dataverse supports a small subset of OData built-in query functions, specifically: contains , endswith , and
startswith . For example, the contains standard query function allows us to filter for properties matching a
string. In this operation, we are querying for all contacts with fullname containing the string (sample) . More
information:Standard query functions.
HTTP Request
GET https://[Organization URI]/api/data/v9.0/contacts?
$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)') HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 4284

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
{
"@odata.etag":"W/\"619718\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"15c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619839\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"1cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619841\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"20c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619843\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"24c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619847\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"2cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619849\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"30c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619851\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"34c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619853\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"38c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619855\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"3cc364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output
Contacts filtered by fullname containing '(sample)':
1) Yvonne McKay (sample), Coffee Master, $45,000.00
2) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
3) Nancy Anderson (sample), Activities Manager, $55,500.00
4) Maria Cambell (sample), Accounts Manager, $31,000.00
5) Nancy Anderson (sample), Logistics Specialist, $63,500.00
6) Scott Konersmann (sample), Accounts Manager, $38,000.00
7) Robert Lyon (sample), Senior Technician, $78,000.00
8) Paul Cannon (sample), Ski Instructor, $68,500.00
9) Rene Valdes (sample), Data Analyst III, $86,000.00
10) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Dataverse query functions


Dataverse query functions provide a large number of options to build queries which are relevant for Dataverse.
For a complete list of these functions, see Web API Query Function Reference. More information:Compose a
query with functions
You will use these query functions in a manner similar to the standard query functions. The main difference is,
when using Dataverse query functions, you must provide the full name of the function including the parameter
name(s). For example, to get a list of contacts created in the last hour, you can construct a query using the
LastXHours Function.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=Microsoft.Dynamics.CRM.LastXHours(PropertyName='createdon',Pr
opertyValue='1') HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 4284

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
{
"@odata.etag":"W/\"619718\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"15c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619839\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"1cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619841\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"20c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619843\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"24c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619847\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"2cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619849\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"30c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619851\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"34c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619853\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"38c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619855\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"3cc364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output

Contacts that were created within the last 1hr:


1) Yvonne McKay (sample), Coffee Master, $45,000.00
2) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
3) Nancy Anderson (sample), Activities Manager, $55,500.00
4) Maria Cambell (sample), Accounts Manager, $31,000.00
5) Nancy Anderson (sample), Logistics Specialist, $63,500.00
6) Scott Konersmann (sample), Accounts Manager, $38,000.00
7) Robert Lyon (sample), Senior Technician, $78,000.00
8) Paul Cannon (sample), Ski Instructor, $68,500.00
9) Rene Valdes (sample), Data Analyst III, $86,000.00
10) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Using operators
Use the Standard filter operators ( eq , ne , gt , ge , lt , le , and , or , not ) to further refine our results. In this
example, we are requesting a list of all contacts with fullname containing (sample) and annual income greater
than 55000 .
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')%20and%20annualincome%20gt%20550
00 HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 2629

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
"value":[
{
"@odata.etag":"W/\"619841\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"20c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619849\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"30c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619851\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"34c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619853\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"38c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619855\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"3cc364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output
Contacts filtered by fullname and annualincome (<$55,000):
1) Nancy Anderson (sample), Activities Manager, $55,500.00
2) Nancy Anderson (sample), Logistics Specialist, $63,500.00
3) Robert Lyon (sample), Senior Technician, $78,000.00
4) Paul Cannon (sample), Ski Instructor, $68,500.00
5) Rene Valdes (sample), Data Analyst III, $86,000.00
6) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Setting precedence
You will use parentheses to establish the order in which your conditions are evaluated.
In this example, we are requesting a list of all contacts with fullname containing (sample) , jobtitle
containing either senior or specialist , and annualincome greater than 55000 . To get the results we want,
parentheses are used to group the jobtitle filters together. Since all operators have the same precedence,
omitting the parentheses will give the or operator the same precedence as the and operators. Filters are
applied from left to right. The order in which these statements appear in the filter can affect the results. This is
what the query in this example looks like:
$filter=contains(fullname,'(sample)') and (contains(jobtitle,'senior') or contains(jobtitle,'specialist'))
and annualincome gt 55000
.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')%20and%20(contains(jobtitle,'sen
ior')%20or%20contains(jobtitle,'specialist'))%20and%20annualincome%20gt%2055000 HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 1393

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619849\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"30c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619855\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"3cc364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output

Contacts filtered by fullname, annualincome and jobtitle (Senior or Specialist):


1) Nancy Anderson (sample), Logistics Specialist, $63,500.00
2) Robert Lyon (sample), Senior Technician, $78,000.00
3) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Ordering results
You can specify either an ascending or descending order on the results by using the $orderby filter option . In
this example, we will query for all contacts with fullname containing (sample) and request the data in
ascending order based on the jobtitle property value and then in descending based on the annualincome
property value using this syntax: $orderby=jobtitle asc, annualincome desc . More information:Order results.
HTTP Request
GET https://[Organization URI]/api/data/v9.0/contacts?
$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')%20&$orderby=jobtitle%20asc,%20a
nnualincome%20desc HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 4284

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
{
"@odata.etag":"W/\"619847\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"2cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619843\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"24c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619841\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"20c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619718\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"15c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619853\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"38c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619855\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"3cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619839\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"1cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619849\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"30c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619851\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"34c364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output
Contacts ordered by jobtitle (ascending) and annualincome (descending):
1) Scott Konersmann (sample), Accounts Manager, $38,000.00
2) Maria Cambell (sample), Accounts Manager, $31,000.00
3) Nancy Anderson (sample), Activities Manager, $55,500.00
4) Yvonne McKay (sample), Coffee Master, $45,000.00
5) Rene Valdes (sample), Data Analyst III, $86,000.00
6) Nancy Anderson (sample), Logistics Specialist, $63,500.00
7) Jim Glynn (sample), Senior International Sales Manager, $81,400.00
8) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
9) Robert Lyon (sample), Senior Technician, $78,000.00
10) Paul Cannon (sample), Ski Instructor, $68,500.00

Parameter alias
Use parameter aliases to more easily reuse parameters in your filters. Parameterized aliases can be used in
$filter and $orderby options. If the alias isn’t assigned a value it is assumed to be null. You can also use
parameter aliases when calling functions. More information:Use Web API functions, Use parameter aliases with
system query options. Taking the order results operation for example, we can write that query again using
parameters and we would get the same output results.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=contains(@p1,'(sample)')%20&$orderby=@p2%20asc,%20@p3%20desc&
@p1=fullname&@p2=jobtitle&@p3=annualincome HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 4284

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
{
"@odata.etag":"W/\"619847\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"2cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619843\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"24c364b2-bf43-e611-80d5-00155da84802"
"contactid":"24c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619841\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"20c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619718\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"15c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619853\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"38c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619855\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"3cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619839\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"1cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619849\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"30c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619851\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"34c364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output

Contacts list using parameterized aliases:


1) Scott Konersmann (sample), Accounts Manager, $38,000.00
2) Maria Cambell (sample), Accounts Manager, $31,000.00
3) Nancy Anderson (sample), Activities Manager, $55,500.00
4) Yvonne McKay (sample), Coffee Master, $45,000.00
5) Rene Valdes (sample), Data Analyst III, $86,000.00
6) Nancy Anderson (sample), Logistics Specialist, $63,500.00
7) Jim Glynn (sample), Senior International Sales Manager, $81,400.00
8) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
9) Robert Lyon (sample), Senior Technician, $78,000.00
10) Paul Cannon (sample), Ski Instructor, $68,500.00

Limit results
Returning more data than you need is bad for performance. The server will return a maximum of 5000 table
rows per request. You can limit the number of results returned using the $top query option or by adding
odata.maxpagesize in the request header. The $top query option only returns the top number of rows from the
result set and ignores the rest. The odata.maxpagesize request header specifies the number of rows returned per
page with an @odata.nextLink property to get results of the next page. For more information about
odata.maxpagesize , see the section on Pagination and see also Limits on number of rows returned.

Top results
We can apply the $top query option to limit the basic query operation to the first five contacts with fullname
containing (sample) . In this case, the request actually produces at least 10 results, but only the first 5 entries are
returned in the response.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')&$top=5 HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Content-Length: 2209

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"value":[
{
"@odata.etag":"W/\"619718\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"15c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619839\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"1cc364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619841\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"20c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619843\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"24c364b2-bf43-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"619845\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"28c364b2-bf43-e611-80d5-00155da84802"
}
]
}

Console output
Contacts top 5 results:
1) Yvonne McKay (sample), Coffee Master, $45,000.00
2) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
3) Nancy Anderson (sample), Activities Manager, $55,500.00
4) Maria Cambell (sample), Accounts Manager, $31,000.00
5) Nancy Anderson (sample), Logistics Specialist, $63,500.00

Result count
You can get just the count of rows from a collection-valued property or a count of matched table rows in a filter.
Getting a count tells us the number of possible rows in our result. However, the Dataverse server will return
5000 as the maximum count even if the result may have more. In this example, we constructed a filter with
jobtitle containing either Senior or Manager and we also requested a $count of the result. The response
contains the count in the @odata.count property as well as the results of the query. More information:Retrieve a
count of table rows.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=contains(jobtitle,'senior')%20or%20contains(jobtitle,%20'mana
ger')&$count=true HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 2654

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"@odata.count":6,
"value":[
{
"@odata.etag":"W/\"620258\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"bf48fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620260\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"c348fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620262\"",
"@odata.etag":"W/\"620262\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"c748fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620266\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"cf48fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620268\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"d348fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620274\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"df48fdee-c143-e611-80d5-00155da84802"
}
]
}

Console output

6 contacts have either 'Manager' or 'Senior' designation in their jobtitle.


Manager or Senior:
1) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
2) Nancy Anderson (sample), Activities Manager, $55,500.00
3) Maria Cambell (sample), Accounts Manager, $31,000.00
4) Scott Konersmann (sample), Accounts Manager, $38,000.00
5) Robert Lyon (sample), Senior Technician, $78,000.00
6) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Pagination
To retrieve a sequential subset of results for a query that returns a large number of rows, use the
odata.maxpagesize instead of $top . More information:Specify the number of rows to return in a page.

In this example, we ask for a $count and we set the odata.maxpagesize to 4 . This filter matches 10 contacts,
but we are only retrieving 4 at a time. We also use the count and the max page size to figured out how many
total pages there are. The result of the first page is returned in this request.
HTTP Request
GET https://[Organization URI]/api/data/v9.0/contacts?
$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')&$count=true HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=4, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=4
Content-Length: 2294

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"@odata.count":10,
"value":[
{
"@odata.etag":"W/\"620138\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b848fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620258\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"bf48fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620260\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"c348fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620262\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"c748fdee-c143-e611-80d5-00155da84802"
}
],
"@odata.nextLink":"https://[Organization URI]/api/data/v9.0/contacts?
$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')&$count=true&$skiptoken=%3Ccooki
e%20pagenumber=%222%22%20pagingcookie=%22%253ccookie%2520page%253d%25221%2522%253e%253ccontactid%2520last%25
3d%2522%257bC748FDEE-C143-E611-80D5-00155DA84802%257d%2522%2520first%253d%2522%257bB848FDEE-C143-E611-80D5-
00155DA84802%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E"
}

Console output
Contacts total: 10 Contacts per page: 4.
Page 1 of 3:
1) Yvonne McKay (sample), Coffee Master, $45,000.00
2) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
3) Nancy Anderson (sample), Activities Manager, $55,500.00
4) Maria Cambell (sample), Accounts Manager, $31,000.00

To retrieve page 2, use a GET request with the value of the @odata.nextLink property.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?


$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')&$count=true&$skiptoken=%3Ccooki
e%20pagenumber=%222%22%20pagingcookie=%22%253ccookie%2520page%253d%25221%2522%253e%253ccontactid%2520last%25
3d%2522%257bC748FDEE-C143-E611-80D5-00155DA84802%257d%2522%2520first%253d%2522%257bB848FDEE-C143-E611-80D5-
00155DA84802%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=4, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=4
Content-Length: 2294

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome)",
"@odata.count":10,
"value":[
{
"@odata.etag":"W/\"620264\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"cb48fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620266\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"cf48fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620268\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"d348fdee-c143-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620270\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"d748fdee-c143-e611-80d5-00155da84802"
}
],
"@odata.nextLink":"https://[Organization URI]/api/data/v9.0/contacts?
$select=fullname,jobtitle,annualincome&$filter=contains(fullname,'(sample)')&$count=true&$skiptoken=%3Ccooki
e%20pagenumber=%223%22%20pagingcookie=%22%253ccookie%2520page%253d%25222%2522%253e%253ccontactid%2520last%25
3d%2522%257bD748FDEE-C143-E611-80D5-00155DA84802%257d%2522%2520first%253d%2522%257bCB48FDEE-C143-E611-80D5-
00155DA84802%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E"
}

Console output
Page 2 of 3:
1) Nancy Anderson (sample), Logistics Specialist, $63,500.00
2) Scott Konersmann (sample), Accounts Manager, $38,000.00
3) Robert Lyon (sample), Senior Technician, $78,000.00
4) Paul Cannon (sample), Ski Instructor, $68,500.00

Expanding results
To retrieve information on associated table rows, use the $expand query option on navigation properties. More
information:Retrieve related rows by expanding navigation properties.
Expand on single -valued navigation property
A Single-valued navigation property represents a many-to-one relationships. In our sample data, the account
has a relationship with a contact via the primarycontactid column (attribute). In this relationship, the account
can only have one primary contact. Using the account EntityType, we can create a query to get information
about the account and expanded information about its primary contact.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/accounts(b2546951-c543-e611-80d5-00155da84802)?


$select=name&$expand=primarycontactid($select=fullname,jobtitle,annualincome) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 700

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,primarycontactid,primarycontactid(fullname,jobtitle,annualincome)
)/$entity",
"@odata.etag":"W/\"620641\"",
"name":"Contoso, Ltd. (sample)",
"accountid":"b2546951-c543-e611-80d5-00155da84802",
"primarycontactid":{
"@odata.etag":"W/\"620534\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b3546951-c543-e611-80d5-00155da84802"
}
}

Console output
Account 'Contoso, Ltd. (sample)' has the following primary contact person:
Fullname: 'Yvonne McKay (sample)'
Jobtitle: 'Coffee Master'
Annualincome: '45000'

Expand on partner property


Each navigation property has a corresponding “partner” property. Once an association is made, we can retrieve
information through this association. Which column we use depends on the base table that the query is against.
For example, in the previous operation, we created a query against the account EntityType and we wanted to get
additional information about its primary contact. We did that via the primarycontactid column (attribute). If we
look up the account EntityType, under the Single-valued navigation properties section, we can see that the
partner property that corresponds to primarycontactid is account_primary_contact collection-valued navigation
property found on the contact EntityType.
Writing a query against a contact, you can expand on the account_primary_contact column to get information
about accounts where this contact is the primary contact. In the sample data, Yvonne McKay (sample) is the
primary contact person for only one account. However, she can potentially be assigned to other accounts as
primary contact. Because the account_primary_contact property has a many-to-one relationship the result is
returned as an array of account rows.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts(b3546951-c543-e611-80d5-00155da84802)?


$select=fullname,jobtitle,annualincome&$expand=account_primary_contact($select=name) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 737

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome,account_primary_contact,account_primary
_contact(name))/$entity",
"@odata.etag":"W/\"620534\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b3546951-c543-e611-80d5-00155da84802",
"account_primary_contact":[
{
"@odata.etag":"W/\"620919\"",
"name":"Contoso, Ltd. (sample)",
"accountid":"b2546951-c543-e611-80d5-00155da84802"
}
]
}
Console output

Contact 'Yvonne McKay (sample)' is the primary contact for the following accounts:
1) Contoso, Ltd. (sample)

Expand on collection-valued navigation property


Collection-valued navigation properties support one-to-many or many-to-many relationships. For example, in
our sample data, the account has a relationship with many contacts via the contact_customer_accounts column
(attribute).
Using the account EntityType, we can create a query to get information about the account and expand
information about its contacts. In this case, the Contoso, Ltd. (sample) is associated to nine other contacts via
the contact_customer_accounts collection-valued navigation property.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/accounts(86546951-c543-e611-80d5-00155da84802)?


$select=name&$expand=contact_customer_accounts($select=fullname,jobtitle,annualincome) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 4073

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,contact_customer_accounts,contact_customer_accounts(fullname,jobt
itle,annualincome))/$entity",
"@odata.etag":"W/\"620921\"",
"name":"Contoso, Ltd. (sample)",
"accountid":"86546951-c543-e611-80d5-00155da84802",
"contact_customer_accounts":[
{
"@odata.etag":"W/\"620847\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"8e546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620849\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"92546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620851\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"96546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620853\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9a546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620855\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9e546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620857\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a2546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620859\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a6546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620861\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"aa546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620863\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"ae546951-c543-e611-80d5-00155da84802"
}
]
]
}

Console output

Account 'Contoso, Ltd. (sample)' has the following contact customers:


1) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
2) Nancy Anderson (sample), Activities Manager, $55,500.00
3) Maria Cambell (sample), Accounts Manager, $31,000.00
4) Nancy Anderson (sample), Logistics Specialist, $63,500.00
5) Scott Konersmann (sample), Accounts Manager, $38,000.00
6) Robert Lyon (sample), Senior Technician, $78,000.00
7) Paul Cannon (sample), Ski Instructor, $68,500.00
8) Rene Valdes (sample), Data Analyst III, $86,000.00
9) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Expand on multiple navigation properties


You can expand on as many navigation properties as the query requires. However, the $expand option can only
go one level deep.
This example expands the primarycontactid , contact_customer_accounts , and Account_Tasks navigation
properties of the account EntityType. This query returns a response containing information about the account
and two collections: a contacts collection and a tasks collection. The sample code will process these collections
separately.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/accounts(86546951-c543-e611-80d5-00155da84802)?


$select=name&$expand=primarycontactid($select=fullname,jobtitle,annualincome),contact_customer_accounts($sel
ect=fullname,jobtitle,annualincome),Account_Tasks($select=subject,description) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Preference-Applied: odata.maxpagesize=10
Content-Length: 5093

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,primarycontactid,contact_customer_accounts,Account_Tasks,primaryc
ontactid(fullname,jobtitle,annualincome),contact_customer_accounts(fullname,jobtitle,annualincome),Account_T
asks(subject,description))/$entity",
"@odata.etag":"W/\"620921\"",
"name":"Contoso, Ltd. (sample)",
"accountid":"86546951-c543-e611-80d5-00155da84802",
"primarycontactid":{
"@odata.etag":"W/\"620726\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"87546951-c543-e611-80d5-00155da84802"
},
"contact_customer_accounts":[
{
"@odata.etag":"W/\"620847\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"8e546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620849\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"92546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620851\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"96546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620853\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9a546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620855\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9e546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620857\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a2546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620859\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a6546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620861\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"aa546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620863\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"ae546951-c543-e611-80d5-00155da84802"
}
],
"Account_Tasks":[
{
"@odata.etag":"W/\"620840\"",
"subject":"Task 1",
"description":"Task 1 description",
"activityid":"8b546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620842\"",
"subject":"Task 2",
"description":"Task 2 description",
"activityid":"8c546951-c543-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"620844\"",
"subject":"Task 3",
"description":"Task 3 description",
"activityid":"8d546951-c543-e611-80d5-00155da84802"
}
]
}

Console output
-- Expanding multiple property types in one request --
Account 'Contoso, Ltd. (sample)' has the following primary contact person:
Fullname: 'Yvonne McKay (sample)'
Jobtitle: 'Coffee Master'
Annualincome: '45000'
Account 'Contoso, Ltd. (sample)' has the following related contacts:
1) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
2) Nancy Anderson (sample), Activities Manager, $55,500.00
3) Maria Cambell (sample), Accounts Manager, $31,000.00
4) Nancy Anderson (sample), Logistics Specialist, $63,500.00
5) Scott Konersmann (sample), Accounts Manager, $38,000.00
6) Robert Lyon (sample), Senior Technician, $78,000.00
7) Paul Cannon (sample), Ski Instructor, $68,500.00
8) Rene Valdes (sample), Data Analyst III, $86,000.00
9) Jim Glynn (sample), Senior International Sales Manager, $81,400.00
Account 'Contoso, Ltd. (sample)' has the following tasks:
1) Task 1, Task 1 description
2) Task 2, Task 2 description
3) Task 3, Task 3 description

FetchXML queries
All the query options we would normally define such as $select , $filter , and $orderby are now defined in
the XML. In this operation, we query for all contacts whose fullname matches (sample) , and order the results
descending by fullname . This is the XML for this query.

<fetch mapping="logical" output-format="xml-platform" version="1.0" distinct="false">


<entity name="contact">
<attribute name="fullname" />
<attribute name="jobtitle" />
<attribute name="annualincome" />
<order descending="true"
attribute="fullname" />
<filter type="and">
<condition value="%(sample)%"
attribute="fullname"
operator="like" />
</filter>
</entity>
</fetch>

HTTP Request
The request query string is sent to the server in encoded form. The encoded header looks like this.

GET https://[Organization URI]/api/data/v9.0/contacts?


fetchXml=%253Cfetch%2520mapping%253D%2522logical%2522%2520output-format%253D%2522xml-
platform%2522%2520version%253D%25221.0%2522%2520distinct%253D%2522false%2522%253E%2520%2520%2520%253Centity%
2520name%253D%2522contact%2522%253E%2520%2520%2520%2520%2520%253Cattribute%2520name%253D%2522fullname%2522%2
520%252F%253E%2520%2520%2520%2520%2520%253Cattribute%2520name%253D%2522jobtitle%2522%2520%252F%253E%2520%252
0%2520%2520%2520%253Cattribute%2520name%253D%2522annualincome%2522%2520%252F%253E%2520%2520%2520%2520%2520%2
53Corder%2520descending%253D%2522true%2522%2520attribute%253D%2522fullname%2522%2520%252F%253E%2520%2520%252
0%2520%2520%253Cfilter%2520type%253D%2522and%2522%253E%2520%2520%2520%2520%2520%2520%2520%253Ccondition%2520
value%253D%2522%2525(sample)%2525%2522%2520attribute%253D%2522fullname%2522%2520operator%253D%2522like%2522%
2520%252F%253E%2520%2520%2520%2520%2520%253C%252Ffilter%253E%2520%2520%2520%253C%252Fentity%253E%2520%253C%2
52Ffetch%253E%2520 HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue
HTTP Response

HTTP/1.1 200 OK
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Content-Length: 4345

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome,_transactioncurrencyid_value,transactio
ncurrencyid,contactid)",
"value":[
{
"@odata.etag":"W/\"621502\"",
"fullname":"Yvonne McKay (sample)",
"jobtitle":"Coffee Master",
"[email protected]":"$45,000.00",
"annualincome":45000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9255b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621627\"",
"fullname":"Susanna Stubberod (sample)",
"jobtitle":"Senior Purchaser",
"[email protected]":"$52,000.00",
"annualincome":52000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9955b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621635\"",
"fullname":"Scott Konersmann (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$38,000.00",
"annualincome":38000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a955b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621637\"",
"fullname":"Robert Lyon (sample)",
"jobtitle":"Senior Technician",
"[email protected]":"$78,000.00",
"annualincome":78000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"ad55b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621641\"",
"fullname":"Rene Valdes (sample)",
"jobtitle":"Data Analyst III",
"[email protected]":"$86,000.00",
"annualincome":86000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b555b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621639\"",
"fullname":"Paul Cannon (sample)",
"jobtitle":"Ski Instructor",
"[email protected]":"$68,500.00",
"annualincome":68500.0000,
"annualincome":68500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b155b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621629\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"9d55b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621633\"",
"fullname":"Nancy Anderson (sample)",
"jobtitle":"Logistics Specialist",
"[email protected]":"$63,500.00",
"annualincome":63500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a555b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621631\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a155b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621643\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b955b257-c843-e611-80d5-00155da84802"
}
]
}

Console output

Contacts Fetched by fullname containing '(sample)':


1) Yvonne McKay (sample), Coffee Master, $45,000.00
2) Susanna Stubberod (sample), Senior Purchaser, $52,000.00
3) Scott Konersmann (sample), Accounts Manager, $38,000.00
4) Robert Lyon (sample), Senior Technician, $78,000.00
5) Rene Valdes (sample), Data Analyst III, $86,000.00
6) Paul Cannon (sample), Ski Instructor, $68,500.00
7) Nancy Anderson (sample), Activities Manager, $55,500.00
8) Nancy Anderson (sample), Logistics Specialist, $63,500.00
9) Maria Cambell (sample), Accounts Manager, $31,000.00
10) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

FetchXML pagination
The way FetchXML handles paging is different than how query filter handles it. In FetchXML, you can specify a
count column that will indicate how many results to return per page. In the same request, you use the page
column to specify the page number you want. This operation will make a request for page 3 from the previous
FetchXML sample. Based on our sample data, we should have ten contacts in our result. Breaking each page
down to only four contacts per page, we should have three pages. Page 3 should contain only two contacts. If we
then ask for page 4, the system will return zero results.

<fetch mapping="logical"
output-format="xml-platform"
version="1.0"
distinct="false"
page="3"
count="4">
<entity name="contact">
<attribute name="fullname" />
<attribute name="jobtitle" />
<attribute name="annualincome" />
<order descending="true"
attribute="fullname" />
<filter type="and">
<condition value="%(sample)%"
attribute="fullname"
operator="like" />
</filter>
</entity>
</fetch>

HTTP Request
The request query string is sent to the server in encoded form. The encoded header looks like this.

GET https://[Organization URI]/api/data/v9.0/contacts?


fetchXml=%253Cfetch%2520mapping%253D%2522logical%2522%2520output-format%253D%2522xml-
platform%2522%2520version%253D%25221.0%2522%2520distinct%253D%2522false%2522%2520page%253D%25223%2522%2520co
unt%253D%25224%2522%253E%2520%2520%2520%253Centity%2520name%253D%2522contact%2522%253E%2520%2520%2520%2520%2
520%253Cattribute%2520name%253D%2522fullname%2522%2520%252F%253E%2520%2520%2520%2520%2520%253Cattribute%2520
name%253D%2522jobtitle%2522%2520%252F%253E%2520%2520%2520%2520%2520%253Cattribute%2520name%253D%2522annualin
come%2522%2520%252F%253E%2520%2520%2520%2520%2520%253Corder%2520descending%253D%2522true%2522%2520attribute%
253D%2522fullname%2522%2520%252F%253E%2520%2520%2520%2520%2520%253Cfilter%2520type%253D%2522and%2522%253E%25
20%2520%2520%2520%2520%2520%2520%253Ccondition%2520value%253D%2522%2525(sample)%2525%2522%2520attribute%253D
%2522fullname%2522%2520operator%253D%2522like%2522%2520%252F%253E%2520%2520%2520%2520%2520%253C%252Ffilter%2
53E%2520%2520%2520%253C%252Fentity%253E%2520%253C%252Ffetch%253E%2520 HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Content-Length: 1037

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,jobtitle,annualincome,_transactioncurrencyid_value,transactio
ncurrencyid,contactid)",
"value":[
{
"@odata.etag":"W/\"621631\"",
"fullname":"Maria Cambell (sample)",
"jobtitle":"Accounts Manager",
"[email protected]":"$31,000.00",
"annualincome":31000.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"a155b257-c843-e611-80d5-00155da84802"
},
{
"@odata.etag":"W/\"621643\"",
"fullname":"Jim Glynn (sample)",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802",
"contactid":"b955b257-c843-e611-80d5-00155da84802"
}
]
}

Console output

Contacts Fetched by fullname containing '(sample)' - Page 3:


1) Maria Cambell (sample), Accounts Manager, $31,000.00
2) Jim Glynn (sample), Senior International Sales Manager, $81,400.00

Predefined queries
You can use the Web API to execute predefined queries. More information:Retrieve and execute predefined
queries.
Saved query
In this operation, we will make a request for the savedqueryid GUID of the saved query named Active
Accounts . Then using the GUID and the savedQuery parameter, we will query for a list of active accounts.
Getting the saved query's GUID.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/savedqueries?


$select=name,savedqueryid&$filter=name%20eq%20'Active%20Accounts' HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue
Referer: https://localhost:1469/WebAPIQuery.html
HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 251

{
"@odata.context":"https://[Organization URI]/api/data/v9.0/$metadata#savedqueries(name,savedqueryid)",
"value":[
{
"@odata.etag":"W/\"443067\"",
"name":"Active Accounts",
"savedqueryid":"00000000-0000-0000-00aa-000010001002"
}
]
}

Getting the saved query's content using the savedQuery parameter


HTTP Request

GET https://[Organization URI]/api/data/v9.0/accounts?savedQuery=00000000-0000-0000-00aa-000010001002


HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
REQ_ID: 2bc532c4-d445-44cd-adae-1909a616d6bc
OData-Version: 4.0
Preference-Applied: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
Content-Length: 446

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,_primarycontactid_value,primarycontactid,accountid)",
"value":[
{
"@odata.etag":"W/\"621613\"",
"name":"Contoso, Ltd. (sample)",
"_primarycontactid_value@OData.Community.Display.V1.FormattedValue":"Yvonne McKay (sample)",
"_primarycontactid_value":"9255b257-c843-e611-80d5-00155da84802",
"accountid":"9155b257-c843-e611-80d5-00155da84802"
}
]
}

Console output

-- Saved Query --
Saved Query (Active Accounts):
1) Contoso, Ltd. (sample)

User query
This sample creates a user query, executes it, then deletes it from the system. This user query is asking for any
contacts whose fullname contains (sample) , jobtitle contains manager , and annualincome greater than
55000 . Our sample data has two contacts matching this query.

Getting the user query's GUID.


HTTP Request

GET https://[Organization URI]/api/data/v9.0/userqueries?


$select=name,userqueryid,&$filter=name%20eq%20'My%20User%20Query' HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Referer: https://localhost:1469/WebAPIQuery.html

HTTP Response

Pragma: no-cache
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 246

{
"@odata.context":"https://[Organization URI]/api/data/v9.0/$metadata#userqueries(name,userqueryid)",
"value":[
{
"@odata.etag":"W/\"621698\"",
"name":"My User Query",
"userqueryid":"7ec390ab-c943-e611-80d5-00155da84802"
}
]
}

Getting the user query's content passing the GUID value with the userQuery parameter.
HTTP Request

GET https://[Organization URI]/api/data/v9.0/contacts?userQuery=7ec390ab-c943-e611-80d5-00155da84802


HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Prefer: odata.maxpagesize=10, odata.include-annotations=OData.Community.Display.V1.FormattedValue

HTTP Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 1040

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#contacts(fullname,contactid,jobtitle,annualincome,_transactioncurrencyid_value,
transactioncurrencyid)",
"value":[
{
"@odata.etag":"W/\"621643\"",
"fullname":"Jim Glynn (sample)",
"contactid":"b955b257-c843-e611-80d5-00155da84802",
"jobtitle":"Senior International Sales Manager",
"[email protected]":"$81,400.00",
"annualincome":81400.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802"
},
{
"@odata.etag":"W/\"621629\"",
"fullname":"Nancy Anderson (sample)",
"contactid":"9d55b257-c843-e611-80d5-00155da84802",
"jobtitle":"Activities Manager",
"[email protected]":"$55,500.00",
"annualincome":55500.0000,
"_transactioncurrencyid_value@OData.Community.Display.V1.FormattedValue":"US Dollar",
"_transactioncurrencyid_value":"518c78c9-d3f6-e511-80d0-00155da84802"
}
]
}

Console output

-- User Query --
Saved User Query:
1) Jim Glynn (sample), Senior International Sales Manager, $81,400.00
2) Nancy Anderson (sample), Activities Manager, $55,500.00

See also
Use the Dataverse Web API
Query Data using the Web API
Retrieve and execute predefined queries
Web API Query Data Sample (C#)
Web API Query Data Sample (Client-side JavaScript)
Web API Conditional Operations Sample
6/18/2021 • 7 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This collection of samples demonstrate how to perform operations that are conditionally based upon the
version of the table row contained on the Microsoft Dataverse server and/or currently maintained by the client.
For more information, see Perform conditional operations using the Web API. This sample is implemented as a
separate project for the following languages:
Web API Conditional Operations Sample (C#)
The Dataverse Web API follows the conventions of the OData v4.0 protocol, which uses ETags to implement
resource version control. Web API conditional operations depend upon this versioning mechanism.
This topic explains the structure and content of the samples at a higher, language-neutral level. It details the
HTTP requests and responses, and the associated program output, where applicable. Review the linked sample
topics above to obtain language-specific implementations and related details about how to perform the
operations described in this topic.

Demonstrates
This sample is divided into three principal sections, listed in the following table. Each section contains a set of
related Web API operations which are discussed in greater detail in the associated conceptual section of the
topic Perform conditional operations using the Web API .

C O DE SEC T IO N A SSO C IAT ED C O N C EP T UA L TO P IC S

Conditional GET Conditional retrievals

Optimistic concurrency on delete and update Apply optimistic concurrency

Controlling upsert operations Limit upsert operations

The following sections contain a brief discussion of the Dataverse Web API operations performed, along with the
corresponding HTTP messages and associated console output which is the same for each language
implementation. For brevity, less pertinent HTTP headers have been omitted. The URIs of the table rows will vary
with the base organization address and the ID of the row assigned by your Dataverse server.

Sample data
The sample creates the following table row before the principal code sections are executed.

EN T IT Y T Y P E C L IEN T - A SSIGN ED P RO P ERT IES SERVER- A SSIGN ED P RO P ERT IES


EN T IT Y T Y P E C L IEN T - A SSIGN ED P RO P ERT IES SERVER- A SSIGN ED P RO P ERT IES

account Name: Contoso Ltd. ID:


Revenue: 5000000 14e151db-9b4f-e611-80e0-
00155da84c08
Telephone: 555-0000
Description: Initial Etag: W/"628448"

Parent company of Contoso


Pharmaceuticals, etc.

Conditional GET
This section of the program demonstrates how to perform conditional retrievals in order to optimize network
bandwidth and server processing while still maintaining the most current row state on the client. More
information:Conditional retrievals
1. Attempt to retrieve the account Contoso Ltd. only if it does not match the current version, identified by the
initial ETag value that was returned when the account row was created. This condition is represented by the
If-None-Match header.

Request

GET https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08)?


$select=name,revenue,telephone1,description HTTP/1.1
If-None-Match: W/"628448"
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

Response

HTTP/1.1 304 Not Modified

Console output

Instance retrieved using ETag: W/"628448"


Expected outcome: Entity was not modified so nothing was returned.

The response value, 304 Not Modified , indicates that the current table row is the most current, so the server
does not return the requested row in the response body.
2. Update the account by modifying its primary telephone number property.
Request

PUT https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08)/telephone1


HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json
{
"value": "555-0001"
}
Response

HTTP/1.1 204 No Content

Console output

Account telephone number updated.

3. Re-attempt the same conditional GET operation, again using the original ETag value. This time the operation
returns the requested data because the version on the server is different (and newer) than the version
identified in the request. As in all table row retrievals, the response includes an ETag header that identifies the
current version.
Request

GET https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08)?


$select=name,revenue,telephone1,description HTTP/1.1
If-None-Match: W/"628448"
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
ETag: W/"628460"
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,revenue,telephone1,description)/$entity",
"@odata.etag":"W/\"628460\"",
"name":"Contoso Ltd",
"revenue":5000000.0000,
"telephone1":"555-0001",
"description":"Parent company of Contoso Pharmaceuticals, etc.",
"accountid":"14e151db-9b4f-e611-80e0-00155da84c08",
"_transactioncurrencyid_value":"0d4ed62e-95f7-e511-80d1-00155da84c03"
}

Console output

Instance retrieved using ETag: W/"628448"


{
"@odata.context": "https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,revenue,telephone1,description)/$entity",
"@odata.etag": "W/\"628460\"",
"name": "Contoso Ltd",
"revenue": 5000000.0,
"telephone1": "555-0001",
"description": "Parent company of Contoso Pharmaceuticals, etc.",
"accountid": "14e151db-9b4f-e611-80e0-00155da84c08",
"_transactioncurrencyid_value": "0d4ed62e-95f7-e511-80d1-00155da84c03"
}

Optimistic concurrency on delete and update


This section of the program demonstrates how to perform conditional delete and update operations. The most
common use for such operations is in implementing an optimistic concurrency approach to row processing in a
multi-user environment. More information:Apply optimistic concurrency
1. Attempt to delete original account if and only if it matches the original version (ETag value). This condition is
represented by the If-Match header. This operation fails because the account row was updated in the
previous section, so as a result, its version was updated on the server.
Request

DELETE https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1


If-Match: W/"628448"
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

Response

HTTP/1.1 412 Precondition Failed


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"error":{
"code":"","message":"The version of the existing record doesn't match the RowVersion property
provided.", . . .
}
}

Console output

Expected Error: The version of the existing record doesn't match the property provided.
Account not deleted using ETag 'W/"628448"', status code: '412'.

2. Attempt to update the account if and only if it matches the original ETag value. Again, this condition is
represented by the If-Match header and the operation fails for the same reason.

Request

PATCH https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1


If-Match: W/"628448"
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
{
"telephone1": "555-0002",
"revenue": 6000000
}

Response
HTTP/1.1 412 Precondition Failed
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"error":{
"code":"","message":"The version of the existing record doesn't match the RowVersion property
provided.", . . .
}
}

Console output

Expected Error: The version of the existing record doesn't match the property provided.
Account not updated using ETag 'W/"628448"', status code: '412'.

3. Re-attempt an update, but instead use the current ETag value obtained from the last row retrieval in the
previous section.
Request

PATCH https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1


If-Match: W/"628460"
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
{
"telephone1": "555-0003",
"revenue": 6000000
}

Response

HTTP/1.1 204 No Content

Console output

Account successfully updated using ETag: W/"628460", status code: '204'.

4. Confirm the update succeeded by retrieving and outputting the current account state. This uses a basic GET
request.
Request

GET https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08)?


$select=name,revenue,telephone1,description HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
ETag: W/"628461"
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,revenue,telephone1,description)/$entity",
"@odata.etag":"W/\"628461\"",
"name":"Contoso Ltd",
"revenue":6000000.0000,
"telephone1":"555-0003",
"description":"Parent company of Contoso Pharmaceuticals, etc.",
"accountid":"14e151db-9b4f-e611-80e0-00155da84c08",
"_transactioncurrencyid_value":"0d4ed62e-95f7-e511-80d1-00155da84c03"
}

Console output

{
"@odata.context": "https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,revenue,telephone1,description)/$entity",
"@odata.etag": "W/\"628461\"",
"name": "Contoso Ltd",
"revenue": 6000000.0,
"telephone1": "555-0003",
"description": "Parent company of Contoso Pharmaceuticals, etc.",
"accountid": "14e151db-9b4f-e611-80e0-00155da84c08",
"_transactioncurrencyid_value": "0d4ed62e-95f7-e511-80d1-00155da84c03"
}

Controlling upsert operations


This section of the program demonstrates how to perform conditional PATCH operations, limiting upsert
operations to perform as either update-only or insert-only operations. More information:Limit upsert operations
1. Attempt to insert, without updating, the primary telephone and revenue properties for this account. The
If-None-Match header with the value of * represents this upsert condition. This operation fails because this
account row still exists on the server (unless it was concurrently deleted by another user or process).
Request

PATCH https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1


If-None-Match: *
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
{
"telephone1": "555-0004",
"revenue": 7500000
}

Response
HTTP/1.1 412 Precondition Failed
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"error":{
"code":"","message":"A record with matching key values already exists.", . . .
}
}

Console output

Expected Error: A record with matching key values already exists.


Account not updated using ETag 'W/"628448", status code: '412'.

2. Attempt to perform the same update operation without creation. To accomplish this, the conditional
If-Match header is used with a value of * . This operation succeeds because the row exists on the server.

Request

PATCH https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1


If-Match: *
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
{
"telephone1": "555-0005",
"revenue": 7500000
}

Response

HTTP/1.1 204 No Content

Console output

Account updated using If-Match '*'

3. Retrieve and output the current account state with a basic GET request. Note that the returned ETag value
has changed to reflect the new, updated version of the account row.
Request

GET https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08)?


$select=name,revenue,telephone1,description HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
ETag: W/"628463"
OData-Version: 4.0
{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,revenue,telephone1,description)/$entity",
"@odata.etag":"W/\"628463\"",
"name":"Contoso Ltd","revenue":7500000.0000,
"telephone1":"555-0005",
"description":"Parent company of Contoso Pharmaceuticals, etc.",
"accountid":"14e151db-9b4f-e611-80e0-00155da84c08",
"_transactioncurrencyid_value":"0d4ed62e-95f7-e511-80d1-00155da84c03"
}

Console output

{
"@odata.context": "https://[Organization
URI]/api/data/v9.0/$metadata#accounts(name,revenue,telephone1,description)/$entity",
"@odata.etag": "W/\"628463\"",
"name": "Contoso Ltd",
"revenue": 7500000.0,
"telephone1": "555-0005",
"description": "Parent company of Contoso Pharmaceuticals, etc.",
"accountid": "14e151db-9b4f-e611-80e0-00155da84c08",
"_transactioncurrencyid_value": "0d4ed62e-95f7-e511-80d1-00155da84c03"
}

4. Delete the account with a basic DELETE .

Request

DELETE https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1


OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

Response

HTTP/1.1 204 No Content

Console output

Account was deleted.

5. Just as in step 2, attempt to update the account if it exists. Again, this condition is represented by the
If-Match header with a value of * . This operation fails because this table row was just deleted. However, if
this If-Match header was absent, then the resulting basic upsert operation should successfully create a new
row.
Request
PATCH https://[Organization URI]/api/data/v9.0/accounts(14e151db-9b4f-e611-80e0-00155da84c08) HTTP/1.1
If-Match: *
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8
{
"telephone1": "555-0006",
"revenue": 7500000
}

Response

HTTP/1.1 404 Not Found


Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
{
"error":{
"code":"","message":"account With Id = 14e151db-9b4f-e611-80e0-00155da84c08 Does Not Exist", . . .
}
}

Console output

Expected Error: Account with Id = 14e151db-9b4f-e611-80e0-00155da84c08 does not exist.


Account not updated because it does not exist, status code: '404'.

There is no need to cleanup sample data because the one account row was already deleted in step 4.
See also
Use the Dataverse Web API
Perform conditional operations using the Web API
Web API Conditional Operations Sample (C#)
Web API Functions and Actions Sample
7/19/2021 • 8 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This group of samples demonstrate how to perform bound and unbound functions and actions, including
custom actions, using the Microsoft Dataverse Web API. This sample is implemented as a separate project for
the following languages:
Functions and Actions Sample (C#)
This topic explains the structure and content of the sample at a higher, language-neutral level. Review the linked
sample topics above for language-specific implementation details about how to perform the operations
described in this topic.

Demonstrates
This sample is divided into the following principal sections, containing Web API functions and actions operations
which are discussed in greater detail in the associated conceptual topics.

TO P IC SEC T IO N A SSO C IAT ED TO P IC ( S)

Sample data

Using unbound function with no parameters Unbound functions

WhoAmI Function

systemuser EntityType

Using unbound function with parameters Unbound functions

GetTimeZoneCodeByLocalizedName Function

Using bound function with no parameters Bound functions

CalculateTotalTimeIncident Function

Using unbound action with parameters Unbound actions

WinOpportunity Action

opportunity EntityType
TO P IC SEC T IO N A SSO C IAT ED TO P IC ( S)

Using bound action with parameters Bound actions

AddToQueue Action

WhoAmI Function

systemuser EntityType

letter EntityType

Using bound custom action with parameters Use a custom action

Bound actions

contact EntityType

Using unbound custom action with parameters Use a custom action

Unbound actions

account EntityType

Handling custom action exceptions Use a custom action

Unbound actions

contact EntityType

The following sections contain a brief discussion of the Dataverse Web API operations performed, along with the
corresponding HTTP messages and associated console output.

Sample data
To ensure the operations in this sample work properly, we first create sample data on the Dataverse server.
These sample data will be deleted from the server unless the user chooses to not delete them. The data in this
sample are created individually as follows.
Create an account (e.g.: Fourth Coffee ) and associate it with an incident that has three 30 minute tasks
(90 minutes total). After the tasks are created, they are then marked as completed. The operation will
calculate the total time it took to complete these three tasks.
{
title: "Sample Case",
"[email protected]": accountUri,
Incident_Tasks: [
{
subject: "Task 1",
actualdurationminutes: 30
},
{
subject: "Task 2",
actualdurationminutes: 30
},
{
subject: "Task 3",
actualdurationminutes: 30
}
]
};

Create an account and associate it with an opportunity. This opportunity will be mark as won in the
sample operation.

{
name: "Sample Account for WebAPIFunctionsAndActions sample",
opportunity_customer_accounts: [{
name: "Opportunity to win"
}]
};

Create a letter activity. The letter will be added to the current user's queue in the sample operation.

{
description: "Example letter"
}

Create a contact to use with a custom action sample_AddNoteToContact in the sample operation.

{
firstname: "Jon",
lastname: "Fogg"
}

Sample operations
The sample operations in this topic are organized in the following ways.
Working with functions: These operations show bound and unbound functions that either accept parameters
or not.
Working with actions: These operations show bound and unbound actions that either accept parameters or
not.
Custom actions: These operations show bound and unbound actions and how to handle custom error
exceptions.

Working with functions


Functions are operations that do not have side effects. A function can be bound to a table row or table (entity
type) collection. Query functions are never bound. For more info, see Use Web API functions. This section shows
samples of how bound and unbound functions are used and how parameters are passed in.

Using unbound function with no parameters


Use an unbound function to retrieve the current user's full name by making use of the WhoAmI Function. This
operation demonstrates how to call an unbound function that does not accept parameters. This operation
returns the current user's full name.
Getting the request and response for the WhoAmI Function.
Request

GET https://[Organization URI]/api/data/v9.0/WhoAmI HTTP/1.1


OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 273

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.WhoAmIResponse",
"BusinessUnitId":"0d6cc84a-d3f6-e511-80d0-00155da84802",
"UserId":"b08dc84a-d3f6-e511-80d0-00155da84802",
"OrganizationId":"0f47eae2-a906-4ae4-9215-f09875979f6a"
}

Using unbound function with parameters


Use an unbound function to retrieve the time zone code. This operation demonstrates how to call an unbound
function that accept parameters. This operation returns the current time zone code for the specified time zone.
More information:Passing parameters to a function
Request

GET https://[Organization
URI]/api/data/v9.0/GetTimeZoneCodeByLocalizedName(LocalizedStandardName=@p1,LocaleId=@p2)?
@p1='Pacific%20Standard%20Time'&@p2=1033 HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 154

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.GetTimeZoneCodeByLocalizedNameResponse",
"TimeZoneCode":4
}

Console output

Unbound function: GetTimeZoneCodeByLocalizedName


Function returned time zone Pacific Standard Time, with code '4'.

Using bound function with no parameters


Use a bound function to retrieve the total time it took to complete all the tasks of an incident. This operation
demonstrates how to call a bound function that does not accept parameters. This operation returns the total
minutes the incident took to close out all its tasks. This function also makes use of the incident data we created
for this sample program. More information:Bound functions
Request

GET https://[Organization URI]/api/data/v9.0/incidents(3d920da5-fb4a-e611-80d5-


00155da84802)/Microsoft.Dynamics.CRM.CalculateTotalTimeIncident() HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 148

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.CalculateTotalTimeIncidentResponse",
"TotalTime":90
}

Console output

Bound function: CalculateTotalTimeIncident


Function returned 90 minutes - total duration of tasks associated with the incident.

Working with actions


Actions are operations that allow side effects. An action is either bound or unbound. For more info, see Use Web
API actions. This section shows samples of how bound and unbound actions are used and how parameters are
passed in. It also shows how custom actions are used and how to handle exceptions from these custom actions.
Using unbound action with parameters
Use an unbound action that takes a set of parameters. This operation closes an opportunity and marks it as won
by calling the WinOpportunity Action. The opportunity EntityType was created as sample data earlier in the
program. More information:Unbound actions
Request

POST https://[Organization URI]/api/data/v9.0/WinOpportunity HTTP/1.1


OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8

{
"Status":3,
"OpportunityClose":{
"subject":"Won Opportunity",
"[email protected]":"https://[Organization URI]/api/data/v9.0/opportunities(47920da5-fb4a-e611-
80d5-00155da84802)"
}
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Console output

Unbound Action: WinOpportunity


Opportunity won.

Using bound action with parameters


Use a bound action that takes parameters. This operation adds a letter to the current user's queue. To accomplish
this, we use the WhoAmI Function and the systemuser EntityType to get a reference to the current user's queue.
We also need reference to the letter EntityType. This letter was created as sample data earlier in the program.
Then the bound AddToQueue Action is called to add the letter to the current user's queue. More
information:Bound actions
Request

POST https://[Organization URI]/api/data/v9.0/queues(1f7bcc50-d3f6-e511-80d0-


00155da84802)/Microsoft.Dynamics.CRM.AddToQueue HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Content-Length: 110

{
"Target":{
"activityid":"4c920da5-fb4a-e611-80d5-00155da84802",
"@odata.type":"Microsoft.Dynamics.CRM.letter"
}
}

Response
HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 170

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.AddToQueueResponse",
"QueueItemId":"67bdfabd-fc4a-e611-80d5-00155da84802"
}

Console output

Bound Action: AddToQueue


QueueItemId returned from AddToQueue Action: 67bdfabd-fc4a-e611-80d5-00155da84802

Working with custom actions


If you define custom actions for your solution, you can call them using the Dataverse Web API. Regardless of
whether the operations included in your custom action have side effects, they can potentially modify data and
therefore are considered actions rather than functions. There is no way to create a custom function. More
information:Use a custom action.
This sample comes with two custom actions. They both require parameters but one is bound and the other is
unbound.
sample_AddNoteToContact : A bound custom action that takes two parameters. One is a NoteTitle and the
other is a NoteText . This custom action adds a note to a contact EntityType. Below is a screen shot of the
Information page for this custom action.
sample_CreateCustomer : An unbound custom action that require different parameters depending on what
type of customer is being created. For example, when the AccountType is "account" then it only requires
AccountName parameter. When the AccountType is "contact", a ContactFirstName and ContactLastName
parameters are required. Below is a screen shot of the Information page for this custom action.

Using bound custom action with parameters


This example calls the sample_AddNoteToContact custom action which is bound to the contact table with the
required parameters. This custom action adds a note to an existing contact. This action returns a row with an
annotationid property. To show that the note was added, the annotationid is used to request information
about the note.
The request and response of the action.
Request
POST https://[Organization URI]/api/data/v9.0/contacts(4d920da5-fb4a-e611-80d5-
00155da84802)/Microsoft.Dynamics.CRM.sample_AddNoteToContact HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Content-Length: 80

{
"NoteTitle":"The Title of the Note",
"NoteText":"The text content of the note."
}

Response

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 149

{
"@odata.context":"https://[Organization URI]/api/data/v9.0/$metadata#annotations/$entity",
"annotationid":"ba146d0b-fd4a-e611-80d5-00155da84802"
}

The request and response of the annotation.


Request

GET https://[Organization URI]/api/data/v9.0/annotations(ba146d0b-fd4a-e611-80d5-00155da84802)?


$select=subject,notetext&$expand=objectid_contact($select=fullname) HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8

Response

HTTP/1.1 200 OK
OData-Version: 4.0
Content-Length: 450

{
"@odata.context":"https://[Organization
URI]/api/data/v9.0/$metadata#annotations(subject,notetext,objectid_contact,objectid_contact(fullname))/$enti
ty",
"@odata.etag":"W/\"622978\"",
"subject":"The Title of the Note",
"notetext":"The text content of the note.",
"annotationid":"ba146d0b-fd4a-e611-80d5-00155da84802",
"objectid_contact":{
"@odata.etag":"W/\"622968\"",
"fullname":"Jon Fogg",
"contactid":"4d920da5-fb4a-e611-80d5-00155da84802"
}
}

Console output
Custom action: sample_AddNoteToContact
A note with the title 'The Title of the Note' and the content 'The text content of the note.' was
created and associated with the contact Jon Fogg.

Using unbound custom action with parameters


This operation calls the sample_CreateCustomer custom action to create an "account" customer. Required
parameters are passed in for a CustomerType of account .
Request

POST https://[Organization URI]/api/data/v9.0/sample_CreateCustomer HTTP/1.1


OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Content-Length: 103

{
"CustomerType":"account",
"AccountName":"Account Customer Created in WebAPIFunctionsAndActions sample"
}

Response

HTTP/1.1 204 No Content


OData-Version: 4.0

Handling custom action exceptions


This example shows that custom actions can return custom error messages. You handle custom exceptions the
same way you handle standard exceptions. To get the custom error message from the sample_CreateCustomer
custom action , this example creates a "contact" customer. However, we intentionally pass in the wrong
parameters for this CustomerType parameter. This operation then catches the exception and displays the error
message, then continues with the sample program.
Request

POST https://[Organization URI]/api/data/v9.0/sample_CreateCustomer HTTP/1.1


OData-MaxVersion: 4.0
OData-Version: 4.0
Content-Type: application/json; charset=utf-8
Content-Length: 103

{
"CustomerType":"contact",
"AccountName":"Account Customer Created in WebAPIFunctionsAndActions sample"
}

Response
HTTP/1.1 500 Internal Server Error
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0
Content-Length: 2760

{
"error":{
"code":"",
"message":"ContactFirstName and ContactLastName are required when CustomerType is contact."
}
}

Console output

Expected custom error: ContactFirstName and ContactLastName are required when CustomerType is contact.

See also
Use the Dataverse Web API
Use Web API functions
Use Web API actions
Web API Functions and Actions Sample (C#)
CDSWebApiService class library (C#)
7/19/2021 • 6 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

A .NET Framework sample class library that uses JSON objects and common HTTP messaging operations with
the Microsoft Dataverse Web API. Use of these class methods result in less complicated application code,
implementation of performance best practices, and improved error processing.
This class library demonstrates how to:
Make your code 'DRY'er by wrapping common operations by Http methods.
Manage an HttpClient in a thread-safe manner.
Manage Service Protection Limit API 429 Too Many Requests errors that a client application should expect.
More information: Service Protection API Limits
Using the provided Visual Studio project, you can build a class library and include this functionality in your own
application code. You can find the CDSWebApiService class library source code and Visual Studio solution at
PowerApps-Samples/cds/webapi/C#/CDSWebApiService.

Example
This example shows how to instantiate a CDSWebAPIService instance and create a contact row.
This example expects that the connection string is set in the App.config file as shown below.

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<startup>
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.7.2" />
</startup>
<connectionStrings>
<add name="Connect"
connectionString="Url=https://yourorg.api.crm.dynamics.com;
Authority=null;
ClientId=51f81489-12ee-4a9e-aaae-a2591f45987d;
RedirectUrl=app://58145B91-0C36-4500-8554-080854F2AC97;
[email protected];
Password=y0urp455w0rd;
CallerObjectId=null;
Version=9.1;
MaxRetries=3;
TimeoutInSeconds=180;
"/>
</connectionStrings>
</configuration>

The code will access the connection string to instantiate the CDSWebApiService.
//Get configuration data from App.config connectionStrings
static readonly string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;
static readonly ServiceConfig config = new ServiceConfig(connectionString);

using (CDSWebApiService svc = new CDSWebApiService(config))


{
//Create a contact
var contact1 = new JObject
{
{ "firstname", "Rafel" },
{ "lastname", "Shillo" }
};
Uri contact1Uri = svc.PostCreate("contacts", contact1);
}

Properties
This class exposes only the BaseAddress property. This is the configured BaseAddress used by the HttpClient. It
can be useful to build complete URIs when needed since most cases will expect relative URIs.

Methods
This class provides the following public methods:

PostCreate
Creates a table row (entity record) synchronously and returns the URI.
Parameters
NAME TYPE DESC RIP T IO N

entitySetName String The entity set name of the type of


entity to create.

body JObject Contains the data for the entity to


create

Return Value
The Uri of the created table row (entity record)
Remarks
This method is provided because creating entities is a common operation and the URI is returned in the
OData-EntityId header. Having this specialized method allows for less code than having only the Post method,
which returns only a JObject.
More information: Create a table row using the Web API.

PostCreateAsync
The asynchronous version of PostCreate.

Post
Sends a POST request synchronously and returns the response as a JObject.
Parameters
NAME TYPE DESC RIP T IO N

path String The relative path to send the request.


Frequently the name of an action or an
entity set name.

body JObject The payload to POST

headers Dictionary<string, List<string>> (Optional) Any headers needed to


apply special behaviors

Return Value
A JObject containing the response.
Remarks
This method can be used for any operation using the POST http method, but it only includes the response
content. Use PostCreate to create table rows (entity records) and return only the URI of the created row.
More information:
Create with data returned
Use Web API actions

PostAsync
The asynchronous version of Post.

Patch
Sends a PATCH request synchronously.
Parameters
NAME TYPE DESC RIP T IO N

uri Uri The relative path to send the request.


Frequently the Uri for a specific table
row (entity record)

body JObject The payload to send

headers Dictionary<string, List<string>> (Optional) Any headers needed to


apply special behaviors

Remarks
Patch is frequently used to Update or Upsert table rows.
More information:
Basic update
Upsert a table

PatchAsync
The asynchronous version of Patch.

Get
Sends a GET request synchronously and returns data
Parameters
NAME TYPE DESC RIP T IO N

path String The relative path of the resource to


return

headers Dictionary<string, List<string>> (Optional) Any headers needed to


apply special behaviors

Return Value
A JToken representing the requested data.
Remarks
More information:
Query Data using the Web API
Retrieve a table row using the Web API
Use Web API functions

GetAsync
The asynchronous version of Get.

Delete
Sends a DELETE request synchronously.
Parameters
NAME TYPE DESC RIP T IO N

uri Uri The relative path of the resource to


delete

headers Dictionary<string, List<string>> (Optional) Any headers needed to


apply special behaviors

Remarks
More information:
Basic delete
Remove a reference to a table
Delete a single property value

DeleteAsync
The asynchronous version of Delete.
Put
Sends a PUT request synchronously.
Parameters
NAME TYPE DESC RIP T IO N

uri Uri The relative path to the resource to


update.

property String The name of the column to update

value String The value to set

Remarks
Put is used to update specific table columns.
Note : The Http PUT method is also used to update table or column definitions (metadata). This method cannot
be used for that purpose. It is specifically for business data.
More information:
Update a single property value
Change the reference in a single-valued navigation property

PutAsync
The asynchronous version of Put.

Private SendAsync method


All of the methods above route their requests through the private SendAsync method. This is where the low
level common logic occurs.
This method contains the logic to manage any Service Protection API 429 errors and re-try them for a number
of times configurable in the service.
In order to do this, it sends a copy of the request rather than the actual request because the request will be
disposed and cannot be sent again if an error is returned.
The copy of the request is available because of the custom HttpRequestMessage Clone method defined in the
Extensions.cs file.

OAuthMessageHandler
When the internal HttpClient is initialized in the CDSWebApiService constructor, an instance of this class is set as
an HttpMessageHandler. This class works with the ADAL libraries to ensure that the accessToken will be
refreshed each time a request is sent. If the accessToken expires, the ADAL library methods will automatically
refresh it.
More information: Example demonstrating a DelegatingHandler

ServiceConfig
The CDSWebApiService class should be initialized with a connection string via the ServiceConfig class.
The ServiceConfig constructor accepts a connection string, typically from the App.config configuration, and the
data defined there is parsed into a ServiceConfig instance which the CDSWebApiService constructor requires.
Properties
The following are the properties of the ServiceConfig class.

NAME TYPE DESC RIP T IO N

Authority String The authority to use to authorize user.


Default is
'https://login.microsoftonline.com/com
mon'

CallerObjectId Guid The Azure AD ObjectId for the user to


impersonate other users.

ClientId String The id of the application registered


with Azure AD

MaxRetries Byte The maximum number of attempts to


retry a request blocked by service
protection limits. Default is 3.

Password SecureString The password for the user principal

RedirectUrl String The Redirect Url of the application


registered with Azure AD

TimeoutInSeconds ushort The amount of time to try completing


a request before it will be cancelled.
Default is 120 (2 minutes)

Url String The Url to the Dataverse environment,


i.e
"https://yourorg.api.crm.dynamics.com
"

UserPrincipalName String The user principal name of the user. i.e.


[email protected]

Version String The version of the Web API to use.


Default is '9.1'

Example connection string


Each of the samples that use CDSWebApiService include a reference to a common App.config and code to read
a connection string value named ' Connect '. The following is an example of that App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.7.2" />
</startup>
<connectionStrings>
<add name="Connect"
connectionString="Url=https://yourorg.api.crm.dynamics.com;
Authority=null;
ClientId=51f81489-12ee-4a9e-aaae-a2591f45987d;
RedirectUrl=app://58145B91-0C36-4500-8554-080854F2AC97;
[email protected];
Password=y0urp455w0rd;
CallerObjectId=null;
Version=9.1;
MaxRetries=3;
TimeoutInSeconds=180;
"/>
</connectionStrings>
</configuration>

The ClientId and RedirectUrl values are for sample applications. You can use these to run the samples, but you
should register your own applications and enter the corresponding values for these properties.
More information: Walkthrough: Register an app with Azure Active Directory

ServiceException
This class simply extends Exception and provides additional properties from an error response.
Properties
NAME TYPE DESC RIP T IO N

Message String The message returned by the platform

ErrorCode Int32 The error code returned by the


platform

StatusCode Int32 The HttpResponseMessage.StatusCode

ReasonPhrase String The


HttpResponseMessage.ReasonPhrase

Samples using this class


The following C# samples use this class:
Basic Operations Sample (C#)
Parallel Operations Sample (C#)
Async Parallel Operations Sample (C#)
Conditional Operations sample (C#)
Query Data sample (C#)
Web API CDSWebApiService Basic Operations
Sample (C#)
7/19/2021 • 7 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to perform basic CRUD (Create, Retrieve, Update, and Delete) and association
and dissociation operations on Microsoft Dataverse table rows (entity records), using the Dataverse Web API.

NOTE
This sample implements the Dataverse operations and console output detailed in Web API Basic Operations Sample and
uses the common C# constructs described in Web API Samples (C#).

Prerequisites
The following is required to build and run the CDSWebApiService C# samples :
Microsoft Visual Studio 2019.
Access to Dataverse with privileges to perform CRUD operations.

How to run this sample


1. Go to the PowerApps-Samples GitHub repository, clone or download the samples repository, and extract
its contents into a local folder.
2. Navigate to the repository folder cds/webapi/C#/BasicOperations, and then open the BasicOperations.sln
file in Visual Studio.
3. In Solution Explorer , under the BasicOperations project, open the App.config file. This is a shared
application configuration file used by all the Web API C# samples. Once you edit this file, you do not have
to edit it again unless you're changing the environment or logon used to run the samples.
4. Edit the Url , UserPrincipalName , and Password values in App.config to set the Dataverse instance and
credentials you want to connect to.

<add name="Connect"
connectionString="Url=https://yourorg.api.crm.dynamics.com;
Authority=null;
ClientId=51f81489-12ee-4a9e-aaae-a2591f45987d;
RedirectUrl=app://58145B91-0C36-4500-8554-080854F2AC97;
[email protected];
Password=y0urp455w0rd;
CallerObjectId=null;
Version=9.1;
MaxRetries=3;
TimeoutInSeconds=180;
"/>
NOTE
The ClientId and RedirectUrl shown above can be used to test the code in this article. You are not required
to register an application just to test the article code.

5. Make sure that the BasicOperations project is set as the startup project. The name of the project should
be bold to indicate it is the startup project. If the name is not bold, right-click it in the solution explorer
and select Set as Star tup Project .
6. Press F5 to run the program in debug mode.

Code listing
This sample depends on the assembly included in the CDSWebAPIService project. For information on the
methods this class provides see: Web API CDSWebApiService class Sample (C#).
The following is the code from the Program.cs file:

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;

namespace PowerApps.Samples
{
internal class Program
{
//Get configuration data from App.config connectionStrings
private static readonly string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;

private static readonly ServiceConfig config = new ServiceConfig(connectionString);

private static void Main()


{
//List of Uris for records created in this sample
List<Uri> entityUris = new List<Uri>();
bool deleteCreatedRecords = true;

try
{
using (CDSWebApiService svc = new CDSWebApiService(config))
{
Console.WriteLine("--Starting Basic Operations--");

#region Section 1: Basic Create and Update operations

Console.WriteLine("--Section 1 started--");
//Create a contact
var contact1 = new JObject
{
{ "firstname", "Rafel" },
{ "lastname", "Shillo" }
};
Uri contact1Uri = svc.PostCreate("contacts", contact1);
Console.WriteLine($"Contact '{contact1["firstname"]} " +
$"{contact1["lastname"]}' created.");
entityUris.Add(contact1Uri); //To delete later
Console.WriteLine($"Contact URI: {contact1Uri}");

//Update a contact
JObject contact1Add = new JObject
{
{ "annualincome", 80000 },
{ "annualincome", 80000 },
{ "jobtitle", "Junior Developer" }
};
svc.Patch(contact1Uri, contact1Add);
Console.WriteLine(
$"Contact '{contact1["firstname"]} {contact1["lastname"]}' " +
$"updated with jobtitle and annual income");

//Retrieve a contact
var retrievedcontact1 = svc.Get(contact1Uri.ToString() +
"?$select=fullname,annualincome,jobtitle,description");
Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' retrieved: \n" +
$"\tAnnual income: {retrievedcontact1["annualincome"]}\n" +
$"\tJob title: {retrievedcontact1["jobtitle"]} \n" +
//description is initialized empty.
$"\tDescription: {retrievedcontact1["description"]}.");

//Modify specific properties and then update entity instance.


JObject contact1Update = new JObject
{
{ "jobtitle", "Senior Developer" },
{ "annualincome", 95000 },
{ "description", "Assignment to-be-determined" }
};
svc.Patch(contact1Uri, contact1Update);

Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' updated:\n" +


$"\tJob title: {contact1Update["jobtitle"]}\n" +
$"\tAnnual income: {contact1Update["annualincome"]}\n" +
$"\tDescription: {contact1Update["description"]}\n");

// Change just one property


string telephone1 = "555-0105";
svc.Put(contact1Uri, "telephone1", telephone1);
Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' " +
$"phone number updated.");

//Now retrieve just the single property.


var telephone1Value = svc.Get($"{contact1Uri}/telephone1");
Console.WriteLine($"Contact's telephone # is: {telephone1Value["value"]}.");

#endregion Section 1: Basic Create and Update operations

#region Section 2: Create record associated to another

/// <summary>
/// Demonstrates creation of entity instance and simultaneous association to another,
/// existing entity.
/// </summary>
///

Console.WriteLine("\n--Section 2 started--");

//Create a new account and associate with existing contact in one operation.
var account1 = new JObject
{
{ "name", "Contoso Ltd" },
{ "telephone1", "555-5555" },
{ "[email protected]", contact1Uri }
};
var account1Uri = svc.PostCreate("accounts", account1);
entityUris.Add(account1Uri); //To delete later
Console.WriteLine($"Account '{account1["name"]}' created.");
Console.WriteLine($"Account URI: {account1Uri}");
//Retrieve account name and primary contact info
JObject retrievedAccount1 = svc.Get($"{account1Uri}?$select=name," +
$"&$expand=primarycontactid($select=fullname,jobtitle,annualincome)") as JObject;

Console.WriteLine($"Account '{retrievedAccount1["name"]}' has primary contact " +


$"'{retrievedAccount1["primarycontactid"]["fullname"]}':");
$"'{retrievedAccount1["primarycontactid"]["fullname"]}':");
Console.WriteLine($"\tJob title: {retrievedAccount1["primarycontactid"]["jobtitle"]} \n"
+
$"\tAnnual income: {retrievedAccount1["primarycontactid"]["annualincome"]}");

#endregion Section 2: Create record associated to another

#region Section 3: Create related entities

/// <summary>
/// Demonstrates creation of entity instance and related entities in a single operation.
/// </summary>
///
Console.WriteLine("\n--Section 3 started--");
//Create the following entries in one operation: an account, its
// associated primary contact, and open tasks for that contact. These
// entity types have the following relationships:
// Accounts
// |---[Primary] Contact (N-to-1)
// |---Tasks (1-to-N)

//Build the Account object inside-out, starting with most nested type(s)
JArray tasks = new JArray();
JObject task1 = new JObject
{
{ "subject", "Sign invoice" },
{ "description", "Invoice #12321" },
{ "scheduledend", DateTimeOffset.Parse("4/19/2019") }
};
tasks.Add(task1);
JObject task2 = new JObject
{
{ "subject", "Setup new display" },
{ "description", "Theme is - Spring is in the air" },
{ "scheduledstart", DateTimeOffset.Parse("4/20/2019") }
};
tasks.Add(task2);
JObject task3 = new JObject
{
{ "subject", "Conduct training" },
{ "description", "Train team on making our new blended coffee" },
{ "scheduledstart", DateTimeOffset.Parse("6/1/2019") }
};
tasks.Add(task3);

JObject contact2 = new JObject


{
{ "firstname", "Susie" },
{ "lastname", "Curtis" },
{ "jobtitle", "Coffee Master" },
{ "annualincome", 48000 },
//Add related tasks using corresponding navigation property
{ "Contact_Tasks", tasks }
};

JObject account2 = new JObject


{
{ "name", "Fourth Coffee" },
//Add related contacts using corresponding navigation property
{ "primarycontactid", contact2 }
};

//Create the account and related records


Uri account2Uri = svc.PostCreate("accounts", account2);
Console.WriteLine($"Account '{account2["name"]} created.");
entityUris.Add(account2Uri); //To delete later
Console.WriteLine($"Contact URI: {account2Uri}");

//Retrieve account, primary contact info, and assigned tasks for contact.
//Dataverse only supports querying-by-expansion one level deep, so first query
// account-primary contact.
var retrievedAccount2 = svc.Get($"{account2Uri}?$select=name," +
$"&$expand=primarycontactid($select=fullname,jobtitle,annualincome)");

Console.WriteLine($"Account '{retrievedAccount2["name"]}' " +


$"has primary contact '{retrievedAccount2["primarycontactid"]["fullname"]}':");

Console.WriteLine($"\tJob title: {retrievedAccount2["primarycontactid"]["jobtitle"]} \n"


+
$"\tAnnual income: {retrievedAccount2["primarycontactid"]["annualincome"]}");

//Next retrieve same contact and its assigned tasks.


//Don't have a saved URI for contact 'Susie Curtis', so create one
// from base address and entity ID.
Uri contact2Uri = new Uri($"
{svc.BaseAddress}contacts({retrievedAccount2["primarycontactid"]["contactid"]})");
//Retrieve the contact
var retrievedcontact2 = svc.Get($"{contact2Uri}?$select=fullname," +
$"&$expand=Contact_Tasks($select=subject,description,scheduledstart,scheduledend)");

Console.WriteLine($"Contact '{retrievedcontact2["fullname"]}' has the following assigned


tasks:");
foreach (JToken tk in retrievedcontact2["Contact_Tasks"])
{
Console.WriteLine(
$"Subject: {tk["subject"]}, \n" +
$"\tDescription: {tk["description"]}\n" +
$"\tStart: {tk["scheduledstart"].Value<DateTime>().ToString("d")}\n" +
$"\tEnd: {tk["scheduledend"].Value<DateTime>().ToString("d")}\n");
}

#endregion Section 3: Create related entities

#region Section 4: Associate and Disassociate entities

/// <summary>
/// Demonstrates associating and disassociating of existing entity instances.
/// </summary>
Console.WriteLine("\n--Section 4 started--");
//Add 'Rafel Shillo' to the contact list of 'Fourth Coffee',
// a 1-to-N relationship.
JObject rel1 = new JObject
{
{ "@odata.id", contact1Uri }
}; //relationship object for msg content
Uri navUri1 = new Uri($"{account2Uri}/contact_customer_accounts/$ref");
//Create relationship
svc.Post(navUri1.ToString(), rel1);
Console.WriteLine($"Contact '{retrievedcontact1["fullname"]}' " +
$"associated to account '{account2["name"]}'.");

//Retrieve and output all contacts for account 'Fourth Coffee'.


var retrievedContactList1 = svc.Get($"{account2Uri}/contact_customer_accounts?" +
$"$select=fullname,jobtitle");

Console.WriteLine($"Contact list for account '{retrievedAccount2["name"]}':");

foreach (JToken ct in retrievedContactList1["value"])


{
Console.WriteLine($"\tName: {ct["fullname"]}, Job title: {ct["jobtitle"]}");
}

//Dissociate the contact from the account. For a collection-valued


// navigation property, must append URI of referenced entity.
Uri dis1Uri = new Uri($"{navUri1}?$id={contact1Uri}");
//Equivalently, could have dissociated from the other end of the
// relationship, using the single-valued navigation ref, located in
// the contact 'Peter Cambel'. This dissociation URI has a simpler form:
// [Org URI]/api/data/v9.1/contacts([contactid#])/parentcustomerid_account/$ref

svc.Delete(dis1Uri);
//'Rafel Shillo' was removed from the the contact list of 'Fourth Coffee'

//Associate an opportunity to a competitor, an N-to-N relationship.


//First, create the required entity instances.
JObject comp1 = new JObject
{
{ "name", "Adventure Works" },
{
"strengths",
"Strong promoter of private tours for multi-day outdoor adventures"
}
};
Uri comp1Uri = svc.PostCreate("competitors", comp1);
entityUris.Add(comp1Uri); //To delete later

JObject oppor1 = new JObject


{
["name"] = "River rafting adventure",
["description"] = "Sales team on a river-rafting offsite and team building"
};
Uri oppor1Uri = svc.PostCreate("opportunities", oppor1);
entityUris.Add(oppor1Uri); //To delete later

//Associate opportunity to competitor via opportunitycompetitors_association.


// navigation property.
JObject rel2 = new JObject
{
{ "@odata.id", comp1Uri }
};
Uri navUri2 = new Uri($"{oppor1Uri}/opportunitycompetitors_association/$ref");

svc.Post(navUri2.ToString(), rel2);
Console.WriteLine($"Opportunity '{oppor1["name"]}' associated with competitor
'{comp1["name"]}'.");

//Retrieve all opportunities for competitor 'Adventure Works'.


var retrievedOpporList1 = svc.Get($"{comp1Uri}?
$select=name,&$expand=opportunitycompetitors_association($select=name,description)");

Console.WriteLine($"Competitor '{retrievedOpporList1["name"]}' has the following


opportunities:");
foreach (JToken op in
retrievedOpporList1["opportunitycompetitors_association"])
{
Console.WriteLine($"\tName: {op["name"]}, \n" +
$"\tDescription: {op["description"]}");
}

//Dissociate opportunity from competitor.


svc.Delete(new Uri($"{navUri2}?$id={comp1Uri}"));
// 'River rafting adventure' opportunity disassociated with 'Adventure Works' competitor

#endregion Section 4: Associate and Disassociate entities

#region Section 5: Delete sample entities

Console.WriteLine("\n--Section 5 started--");
//Delete all the created sample entities. Note that explicit deletion is not required
// for contact tasks because these are automatically cascade-deleted with owner.

if (!deleteCreatedRecords)
{
Console.Write("\nDo you want these entity records deleted? (y/n) [y]: ");
String answer = Console.ReadLine();
answer = answer.Trim();
if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty))
if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty))
{ entityUris.Clear(); }
else
{
Console.WriteLine("\nDeleting created records.");
}
}
else
{
Console.WriteLine("\nDeleting created records.");
}

foreach (Uri entityUrl in entityUris)


{
svc.Delete(entityUrl);
}

#endregion Section 5: Delete sample entities

Console.WriteLine("--Basic Operations Completed--");


Console.WriteLine("Press any key to close");
Console.ReadLine();
}
}
catch (Exception)
{
throw;
}
}
}
}

See also
Use the Dataverse Web API
Web API CDSWebApiService class Sample (C#)
Create a table row using the Web API
Update and delete table rows using the Web API
Retrieve an table row using the Web API
Web API Samples
Web API Basic Operations Sample Web API Query Data Sample (C#)
Web API Conditional Operations Sample (C#)
Web API Functions and Actions Sample (C#)
Query Data sample (C#)
7/19/2021 • 15 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to perform data queries of Microsoft Dataverse table rows (entity records), using
the Dataverse Web API. Those query operations include:
Selecting specific properties
Using Query Functions
Ordering and alias
Limit results
Expanding results
Aggregate results
FetchXml queries
Using predefined queries

NOTE
This sample implements the Dataverse operations and console output detailed in Web API Basic Operations Sample and
uses the methods available in the CDSWebApiService class for message processing, performance enhancements, and error
management.

Prerequisites
The following is required to build and run the sample:
Microsoft Visual Studio 2019.
Access to Dataverse with privileges to perform the operations described above.

How to run this sample


1. Go to the PowerApps-Samples repository and either clone or download the compressed samples
repository. If you downloaded the compressed file, extract its contents into a local folder.
2. Navigate to the cds/webapi/C#/QueryData folder and load the solution file into Visual Studio.
3. Edit the App.config file that is shared by several of the samples and set appropriate values for the
Dataverse environment you intend to use: connectionString Url , UserPrincipalName , and Password . You
only need to perform this step once for all samples that share this file.
4. Press F5 to build and run the program in debug mode.

Code listing
This sample depends on the assembly built from in the CDSWebApiService project. For information on the
methods this class provides see CDSWebApiService class.
The following is the code from the Program.cs file:

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;

namespace PowerApps.Samples
{
internal class Program
{
//Get configuration data from App.config connectionStrings
private static readonly string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;

private static readonly ServiceConfig config = new ServiceConfig(connectionString);

//Centralized collection of absolute URIs for created entity instances


private static readonly List<Uri> entityUris = new List<Uri>();

//Uri for records referenced in this sample


private static Uri account1Uri, contact1Uri;

//Control whether records created for this sample should be deleted.


private static readonly bool deleteCreatedRecords = true;

private static void Main()


{
try
{
using (CDSWebApiService svc = new CDSWebApiService(config))
{
Console.WriteLine("\n--Starting Query Data --");
//Create the records that this sample will query.
CreateRequiredRecords(svc);

//Get the id and name of the account created to use as a filter.


var contoso = svc.Get($"{account1Uri}?$select=accountid,name");
var contosoId = Guid.Parse(contoso["accountid"].ToString());
string contosoName = (string)contoso["name"];

#region Selecting specific properties

// Basic query: Query using $select against a contact entity to get the properties you
want.
// For performance best practice, always use $select, otherwise all properties are
returned
Console.WriteLine("-- Basic Query --");

//Header required to include formatted values


var formattedValueHeaders = new Dictionary<string, List<string>> {
{ "Prefer", new List<string>
{ "odata.include-annotations=\"OData.Community.Display.V1.FormattedValue\"" }
}
};

var contact1 = svc.Get(


$"{contact1Uri}?$select=fullname,jobtitle,annualincome",
formattedValueHeaders);

Console.WriteLine($"Contact basic info:\n" +


$"\tFullname: {contact1["fullname"]}\n" +
$"\tJobtitle: {contact1["jobtitle"]}\n" +
$"\tAnnualincome (unformatted): {contact1["annualincome"]} \n" +
$"\tAnnualincome (formatted):
{contact1["[email protected]"]} \n");
#endregion Selecting specific properties

#region Using query functions

// Filter criteria:
// Applying filters to get targeted data.
// 1) Using standard query functions (e.g.: contains, endswith, startswith)
// 2) Using Dataverse query functions (e.g.: LastXhours, Last7Days, Today, Between, In,
...)
// 3) Using filter operators and logical operators (e.g.: eq, ne, gt, and, or, etc…)
// 4) Set precedence using parenthesis (e.g.: ((criteria1) and (criteria2)) or
(criteria3)
// For more info, see:
//https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/query-data-
web-api#filter-results

Console.WriteLine("-- Filter Criteria --");


//Filter 1: Using standard query functions to filter results. In this operation, we
//will query for all contacts with fullname containing the string "(sample)".
JToken containsSampleinFullNameCollection = svc.Get("contacts?" +
"$select=fullname,jobtitle,annualincome&" +
"$filter=contains(fullname,'(sample)') and " +
$"_parentcustomerid_value eq {contosoId.ToString()}",
formattedValueHeaders);

WriteContactResultsTable(
"Contacts filtered by fullname containing '(sample)':",
containsSampleinFullNameCollection["value"]);

//Filter 2: Using Dataverse query functions to filter results. In this operation, we


will query
//for all contacts that were created in the last hour. For complete list of Dataverse
query
//functions, see: https://docs.microsoft.com/dynamics365/customer-engagement/web-
api/queryfunctions

JToken createdInLastHourCollection = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$filter=Microsoft.Dynamics.CRM.LastXHours(PropertyName='createdon',PropertyValue='1')
and " +
$"_parentcustomerid_value eq {contosoId.ToString()}",
formattedValueHeaders);

WriteContactResultsTable(
"Contacts that were created within the last 1hr:",
createdInLastHourCollection["value"]);

//Filter 3: Using operators. Building on the previous operation, we further limit


//the results by the contact's income. For more info on standard filter operators,
//https://docs.microsoft.com/powerapps/developer/data-platform/webapi/query-data-web-
api#filter-results

JToken highIncomeContacts = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$filter=contains(fullname,'(sample)') and " +
"annualincome gt 55000 and " +
$"_parentcustomerid_value eq {contosoId.ToString()}",
formattedValueHeaders);

WriteContactResultsTable(
"Contacts with '(sample)' in name and income above $55,000:",
highIncomeContacts["value"]);

//Filter 4: Set precedence using parentheses. Continue building on the previous


//operation, we further limit results by job title. Parentheses and the order of
//filter statements can impact results returned.

JToken seniorOrSpecialistsCollection = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$select=fullname,jobtitle,annualincome&" +
"$filter=contains(fullname,'(sample)') and " +
"(contains(jobtitle, 'senior') or " +
"contains(jobtitle,'manager')) and " +
"annualincome gt 55000 and " +
$"_parentcustomerid_value eq {contosoId.ToString()}",
formattedValueHeaders);

WriteContactResultsTable(
"Contacts with '(sample)' in name senior jobtitle or high income:",
seniorOrSpecialistsCollection["value"]);

#endregion Using query functions

#region Ordering and aliases

//Results can be ordered in descending or ascending order.


Console.WriteLine("\n-- Order Results --");

JToken orderedResults = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$filter=contains(fullname,'(sample)')and " +
$"_parentcustomerid_value eq {contosoId.ToString()}&" +
"$orderby=jobtitle asc, annualincome desc",
formattedValueHeaders);

WriteContactResultsTable(
"Contacts ordered by jobtitle (Ascending) and annualincome (descending)",
orderedResults["value"]);

//Parameterized aliases can be used as parameters in a query. These parameters can be


used
//in $filter and $orderby options. Using the previous operation as basis, parameterizing
the
//query will give us the same results. For more info, see:
//https://docs.microsoft.com/powerapps/developer/data-platform/webapi/use-web-api-
functions#passing-parameters-to-a-function

Console.WriteLine("\n-- Parameterized Aliases --");

JToken orderedResultsWithParams = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$filter=contains(@p1,'(sample)') and " +
"@p2 eq @p3&" +
"$orderby=@p4 asc, @p5 desc&" +
"@p1=fullname&" +
"@p2=_parentcustomerid_value&" +
$"@p3={contosoId.ToString()}&" +
"@p4=jobtitle&" +
"@p5=annualincome",
formattedValueHeaders);

WriteContactResultsTable(
"Contacts ordered by jobtitle (Ascending) and annualincome (descending)",
orderedResultsWithParams["value"]);

#endregion Ordering and aliases

#region Limit results

//To limit records returned, use the $top query option. Specifying a limit number for
$top
//returns at most that number of results per request. Extra results are ignored.
//For more information, see:
// https://docs.microsoft.com/powerapps/developer/data-platform/webapi/query-data-web-
api#use-top-query-option
Console.WriteLine("\n-- Top Results --");

JToken topFive = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$select=fullname,jobtitle,annualincome&" +
"$filter=contains(fullname,'(sample)') and " +
$"_parentcustomerid_value eq {contosoId.ToString()}&" +
"$top=5",
formattedValueHeaders);

WriteContactResultsTable("Contacts top 5 results:", topFive["value"]);

//Result count - count the number of results matching the filter criteria.
//Tip: Use count together with the "odata.maxpagesize" to calculate the number of pages
in
//the query. Note: Dataverse has a max record limit of 5000 records per response.
Console.WriteLine("\n-- Result Count --");
//1) Get a count of a collection without the data.
JToken count = svc.Get($"contacts/$count");
Console.WriteLine($"\nThe contacts collection has {count} contacts.");
// 2) Get a count along with the data.

JToken countWithData = svc.Get("contacts?" +


"$select=fullname,jobtitle,annualincome&" +
"$filter=(contains(jobtitle,'senior') or contains(jobtitle, 'manager')) and " +
$"_parentcustomerid_value eq {contosoId.ToString()}" +
"&$count=true",
formattedValueHeaders);

WriteContactResultsTable($"{countWithData["@odata.count"]} " +
$"Contacts with 'senior' or 'manager' in job title:",
countWithData["value"]);

#endregion Limit results

#region Expanding results

//The expand option retrieves related information.


//To retrieve information on associated entities in the same request, use the $expand
//query option on navigation properties.
// 1) Expand using single-valued navigation properties (e.g.: via the
'primarycontactid')
// 2) Expand using partner property (e.g.: from contact to account via the
'account_primary_contact')
// 3) Expand using collection-valued navigation properties (e.g.: via the
'contact_customer_accounts')
// 4) Expand using multiple navigation property types in a single request.
// 5) Multi-level expands

// Tip: For performance best practice, always use $select statement in an expand option.
Console.WriteLine("\n-- Expanding Results --");

//1) Expand using the 'primarycontactid' single-valued navigation property of account1.

JToken account1 = svc.Get($"{account1Uri}?" +


"$select=name&" +
"$expand=primarycontactid($select=fullname,jobtitle,annualincome)");

Console.WriteLine($"\nAccount {account1["name"]} has the following primary contact


person:\n" +
$"\tFullname: {account1["primarycontactid"]["fullname"]} \n" +
$"\tJobtitle: {account1["primarycontactid"]["jobtitle"]} \n" +
$"\tAnnualincome: { account1["primarycontactid"]["annualincome"]}");

//2) Expand using the 'account_primary_contact' partner property.

JToken contact2 = svc.Get($"{contact1Uri}?$select=fullname,jobtitle,annualincome&" +


"$expand=account_primary_contact($select=name)");

Console.WriteLine($"\nContact '{contact2["fullname"]}' is the primary contact for the


following accounts:");
foreach (JObject account in contact2["account_primary_contact"])
{
Console.WriteLine($"\t{account["name"]}");
Console.WriteLine($"\t{account["name"]}");
}

//3) Expand using the collection-valued 'contact_customer_accounts' navigation property.

JToken account2 = svc.Get($"{account1Uri}?" +


"$select=name&" +
"$expand=contact_customer_accounts($select=fullname,jobtitle,annualincome)",
formattedValueHeaders);

WriteContactResultsTable(
$"Account '{account2["name"]}' has the following contact customers:",
account2["contact_customer_accounts"]);

//4) Expand using multiple navigation property types in a single request, specifically:
// primarycontactid, contact_customer_accounts, and Account_Tasks.

Console.WriteLine("\n-- Expanding multiple property types in one request -- ");

JToken account3 = svc.Get($"{account1Uri}?$select=name&" +


"$expand=primarycontactid($select=fullname,jobtitle,annualincome)," +
"contact_customer_accounts($select=fullname,jobtitle,annualincome)," +
"Account_Tasks($select=subject,description)",
formattedValueHeaders);

Console.WriteLine($"\nAccount {account3["name"]} has the following primary contact


person:\n" +
$"\tFullname: {account3["primarycontactid"]["fullname"]} \n" +
$"\tJobtitle: {account3["primarycontactid"]["jobtitle"]} \n" +
$"\tAnnualincome: {account3["primarycontactid"]["annualincome"]}");

WriteContactResultsTable(
$"Account '{account3["name"]}' has the following contact customers:",
account3["contact_customer_accounts"]);

Console.WriteLine($"\nAccount '{account3["name"] }' has the following tasks:");

foreach (JObject task in account3["Account_Tasks"])


{
Console.WriteLine($"\t{task["subject"]}");
}

// 5) Multi-level expands

//The following query applies nested expands to single-valued navigation properties


// starting with Task entities related to contacts created for this sample.
JToken contosoTasks = svc.Get($"tasks?" +
$"$select=subject&" +
$"$filter=regardingobjectid_contact_task/_accountid_value eq {contosoId}" +
$"&$expand=regardingobjectid_contact_task($select=fullname;" +
$"$expand=parentcustomerid_account($select=name;" +
$"$expand=createdby($select=fullname)))",
formattedValueHeaders);

Console.WriteLine("\nExpanded values from Task:");

DisplayExpandedValuesFromTask(contosoTasks["value"]);

#endregion Expanding results

#region Aggregate results

//Get aggregated salary information about Contacts working for Contoso

Console.WriteLine("\nAggregated Annual Income information for Contoso contacts:");

JToken contactData = svc.Get($"{account1Uri}/contact_customer_accounts?" +


$"$apply=aggregate(annualincome with average as average, " +
$"annualincome with sum as total, " +
$"annualincome with min as minimum, " +
$"annualincome with min as minimum, " +
$"annualincome with max as maximum)", formattedValueHeaders);

Console.WriteLine($"\tAverage income: {contactData["value"][0]


["[email protected]"]}");
Console.WriteLine($"\tTotal income: {contactData["value"][0]
["[email protected]"]}");
Console.WriteLine($"\tMinium income: {contactData["value"][0]
["[email protected]"]}");
Console.WriteLine($"\tMaximum income: {contactData["value"][0]
["[email protected]"]}");

#endregion Aggregate results

#region FetchXML queries

//Use FetchXML to query for all contacts whose fullname contains '(sample)'.
//Note: XML string must be URI encoded. For more information, see:
//https://docs.microsoft.com/powerapps/developer/data-platform/webapi/retrieve-and-
execute-predefined-queries#use-custom-fetchxml
Console.WriteLine("\n-- FetchXML -- ");
string fetchXmlQuery =
"<fetch mapping='logical' output-format='xml-platform' version='1.0'
distinct='false'>" +
"<entity name ='contact'>" +
"<attribute name ='fullname' />" +
"<attribute name ='jobtitle' />" +
"<attribute name ='annualincome' />" +
"<order descending ='true' attribute='fullname' />" +
"<filter type ='and'>" +
"<condition value ='%(sample)%' attribute='fullname' operator='like' />" +
$"<condition value ='{contosoId.ToString()}' attribute='parentcustomerid'
operator='eq' />" +
"</filter>" +
"</entity>" +
"</fetch>";
JToken contacts = svc.Get(
$"contacts?fetchXml={WebUtility.UrlEncode(fetchXmlQuery)}",
formattedValueHeaders);

WriteContactResultsTable($"Contacts Fetched by fullname containing '(sample)':",


contacts["value"]);

#endregion FetchXML queries

#region Using predefined queries

//Use predefined queries of the following two types:


// 1) Saved query (system view)
// 2) User query (saved view)
//For more info, see:
//https://docs.microsoft.com/powerapps/developer/data-platform/webapi/retrieve-and-
execute-predefined-queries#predefined-queries

//1) Saved Query - retrieve "Active Accounts", run it, then display the results.
Console.WriteLine("\n-- Saved Query -- ");

JToken savedqueryid = svc.Get("savedqueries?" +


"$select=name,savedqueryid&" +
"$filter=name eq 'Active Accounts'")["value"][0]["savedqueryid"];

var activeAccounts = svc.Get(


$"accounts?savedQuery={savedqueryid}",
formattedValueHeaders)["value"] as JArray;

DisplayFormattedEntities("\nActive Accounts", activeAccounts, new string[] { "name" });


//2) Create a user query, then retrieve and execute it to display its results.
//For more info, see:
//https://docs.microsoft.com/powerapps/developer/data-platform/saved-queries
Console.WriteLine("\n-- User Query -- ");
var userQuery = new JObject
{
["name"] = "My User Query",
["description"] = "User query to display contact info.",
["querytype"] = 0,
["returnedtypecode"] = "contact",
["fetchxml"] = @"<fetch mapping='logical' output-format='xml-platform' version='1.0'
distinct='false'>
<entity name ='contact'>
<attribute name ='fullname' />
<attribute name ='contactid' />
<attribute name ='jobtitle' />
<attribute name ='annualincome' />
<order descending ='false' attribute='fullname' />
<filter type ='and'>
<condition value ='%(sample)%' attribute='fullname' operator='like' />
<condition value ='%Manager%' attribute='jobtitle' operator='like' />
<condition value ='55000' attribute='annualincome' operator='gt' />
</filter>
</entity>
</fetch>"
};

//Create the saved query


Uri myUserQueryUri = svc.PostCreate("userqueries", userQuery);
entityUris.Add(myUserQueryUri); //To delete later
//Retrieve the userqueryid
JToken myUserQueryId = svc.Get($"{myUserQueryUri}/userqueryid")["value"];
//Use the query to return results:
JToken myUserQueryResults = svc.Get($"contacts?userQuery={myUserQueryId}",
formattedValueHeaders)["value"];

WriteContactResultsTable($"Contacts Fetched by My User Query:", myUserQueryResults);

#endregion Using predefined queries

DeleteRequiredRecords(svc, deleteCreatedRecords);

Console.WriteLine("\n--Query Data Completed--");


Console.WriteLine("Press any key to close");
Console.ReadLine();
}
}
catch (Exception)
{
throw;
}
}

private static void CreateRequiredRecords(CDSWebApiService svc)


{
var account1 = new JObject {
{ "name", "Contoso, Ltd. (sample)" },
{ "Account_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Contoso, Ltd."},
{ "description","Task 1 for Contoso, Ltd. description"},
{ "actualdurationminutes", 10 }},
new JObject{
{ "subject","Task 2 for Contoso, Ltd."},
{ "description","Task 2 for Contoso, Ltd. description"},
{ "actualdurationminutes", 10 }},
new JObject{
{ "subject","Task 3 for Contoso, Ltd."},
{ "description","Task 3 for Contoso, Ltd. description"},
{ "description","Task 3 for Contoso, Ltd. description"},
{ "actualdurationminutes", 10 }},
}
},
{ "primarycontactid", new JObject{
{ "firstname", "Yvonne" },
{ "lastname", "McKay (sample)" },
{ "jobtitle", "Coffee Master" },
{ "annualincome", 45000 },
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Yvonne McKay"},
{ "description","Task 1 for Yvonne McKay description"},
{ "actualdurationminutes", 5 }},
new JObject{
{ "subject","Task 2 for Yvonne McKay"},
{ "description","Task 2 for Yvonne McKay description"},
{ "actualdurationminutes", 5 }},
new JObject{
{ "subject","Task 3 for Yvonne McKay"},
{ "description","Task 3 for Yvonne McKay description"},
{ "actualdurationminutes", 5 }},
}
}
}
},
{ "contact_customer_accounts", new JArray{
new JObject{
{ "firstname","Susanna"},
{ "lastname","Stubberod (sample)"},
{ "jobtitle","Senior Purchaser"},
{ "annualincome", 52000},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Susanna Stubberod"},
{ "description","Task 1 for Susanna Stubberod description"},
{ "actualdurationminutes", 3 }},
new JObject{
{ "subject","Task 2 for Susanna Stubberod"},
{ "description","Task 2 for Susanna Stubberod description"},
{ "actualdurationminutes", 3 }},
new JObject{
{ "subject","Task 3 for Susanna Stubberod"},
{ "description","Task 3 for Susanna Stubberod description"},
{ "actualdurationminutes", 3 }},
}
}},
new JObject{
{ "firstname","Nancy"},
{ "lastname","Anderson (sample)"},
{ "jobtitle","Activities Manager"},
{ "annualincome", 55500},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Nancy Anderson"},
{ "description","Task 1 for Nancy Anderson description"},
{ "actualdurationminutes", 4 }},
new JObject{
{ "subject","Task 2 for Nancy Anderson"},
{ "description","Task 2 for Nancy Anderson description"},
{ "actualdurationminutes", 4 }},
new JObject{
{ "subject","Task 3 for Nancy Anderson"},
{ "description","Task 3 for Nancy Anderson description"},
{ "actualdurationminutes", 4 }},
}
}},
new JObject{
{ "firstname","Maria"},
{ "lastname","Cambell (sample)"},
{ "lastname","Cambell (sample)"},
{ "jobtitle","Accounts Manager"},
{ "annualincome", 31000},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Maria Cambell"},
{ "description","Task 1 for Maria Cambell description"},
{ "actualdurationminutes", 5 }},
new JObject{
{ "subject","Task 2 for Maria Cambell"},
{ "description","Task 2 for Maria Cambell description"},
{ "actualdurationminutes", 5 }},
new JObject{
{ "subject","Task 3 for Maria Cambell"},
{ "description","Task 3 for Maria Cambell description"},
{ "actualdurationminutes", 5 }},
}
}},
new JObject{
{ "firstname","Scott"},
{ "lastname","Konersmann (sample)"},
{ "jobtitle","Accounts Manager"},
{ "annualincome", 38000},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Scott Konersmann"},
{ "description","Task 1 for Scott Konersmann description"},
{ "actualdurationminutes", 6 }},
new JObject{
{ "subject","Task 2 for Scott Konersmann"},
{ "description","Task 2 for Scott Konersmann description"},
{ "actualdurationminutes", 6 }},
new JObject{
{ "subject","Task 3 for Scott Konersmann"},
{ "description","Task 3 for Scott Konersmann description"},
{ "actualdurationminutes", 6 }},
}
}},
new JObject{
{ "firstname","Robert"},
{ "lastname","Lyon (sample)"},
{ "jobtitle","Senior Technician"},
{ "annualincome", 78000},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Robert Lyon"},
{ "description","Task 1 for Robert Lyon description"},
{ "actualdurationminutes", 7 }},
new JObject{
{ "subject","Task 2 for Robert Lyon"},
{ "description","Task 2 for Robert Lyon description"},
{ "actualdurationminutes", 7 }},
new JObject{
{ "subject","Task 3 for Robert Lyon"},
{ "description","Task 3 for Robert Lyon description"},
{ "actualdurationminutes", 7 }},
}
}},
new JObject{
{ "firstname","Paul"},
{ "lastname","Cannon (sample)"},
{ "jobtitle","Ski Instructor"},
{ "annualincome", 68500},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Paul Cannon"},
{ "description","Task 1 for Paul Cannon description"},
{ "actualdurationminutes", 8 }},
new JObject{
{ "subject","Task 2 for Paul Cannon"},
{ "subject","Task 2 for Paul Cannon"},
{ "description","Task 2 for Paul Cannon description"},
{ "actualdurationminutes", 8 }},
new JObject{
{ "subject","Task 3 for Paul Cannon"},
{ "description","Task 3 for Paul Cannon description"},
{ "actualdurationminutes", 8 }},
}
}},
new JObject{
{ "firstname","Rene"},
{ "lastname","Valdes (sample)"},
{ "jobtitle","Data Analyst III"},
{ "annualincome", 86000},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Rene Valdes"},
{ "description","Task 1 for Rene Valdes description"},
{ "actualdurationminutes", 9 }},
new JObject{
{ "subject","Task 2 for Rene Valdes"},
{ "description","Task 2 for Rene Valdes description"},
{ "actualdurationminutes", 9 }},
new JObject{
{ "subject","Task 3 for Rene Valdes"},
{ "description","Task 3 for Rene Valdes description"},
{ "actualdurationminutes", 9 }},
}
}},
new JObject{
{ "firstname","Jim"},
{ "lastname","Glynn (sample)"},
{ "jobtitle","Senior International Sales Manager"},
{ "annualincome", 81400},
{ "Contact_Tasks", new JArray{
new JObject{
{ "subject","Task 1 for Jim Glynn"},
{ "description","Task 1 for Jim Glynn description"},
{ "actualdurationminutes", 10 }},
new JObject{
{ "subject","Task 2 for Jim Glynn"},
{ "description","Task 2 for Jim Glynn description"},
{ "actualdurationminutes", 10 }},
new JObject{
{ "subject","Task 3 for Jim Glynn"},
{ "description","Task 3 for Jim Glynn description"},
{ "actualdurationminutes", 10 }},
}
}}
}}
};

account1Uri = svc.PostCreate("accounts", account1);


entityUris.Add(account1Uri);
contact1Uri = new Uri((svc.Get($"{account1Uri}/primarycontactid/$ref"))
["@odata.id"].ToString());
entityUris.Add(contact1Uri);
var contact_customer_accounts = svc.Get($"{account1Uri}/contact_customer_accounts/$ref");
foreach (JObject contactRef in contact_customer_accounts["value"])
{
//Add to the top of the list so these are deleted first
entityUris.Insert(0, new Uri(contactRef["@odata.id"].ToString()));
}
//The related tasks will be deleted automatically when the owning record is deleted.
Console.WriteLine("Account 'Contoso, Ltd. (sample)' created with 1 primary " +
"contact and 8 associated contacts.");
}

private static void DeleteRequiredRecords(CDSWebApiService svc, bool deleteCreatedRecords)


{
{
if (!deleteCreatedRecords)
{
Console.Write("\nDo you want these sample entity records deleted? (y/n) [y]: ");
string answer = Console.ReadLine();
answer = answer.Trim();
if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty))
{
return;
}
}

Console.WriteLine("\nDeleting data created for this sample:");

entityUris.ForEach(x =>
{
Console.Write(".");
svc.Delete(x);
});

Console.WriteLine("\nData created for this sample deleted.");


}

private static void WriteContactResultsTable(string message, JToken collection)


{
//Display column widths for contact results table
const int col1 = -27;
const int col2 = -35;
const int col3 = -15;

Console.WriteLine($"\n{message}");
//header
Console.WriteLine($"\t|{"Full Name",col1}|" +
$"{"Job Title",col2}|" +
$"{"Annual Income",col3}");
Console.WriteLine($"\t|{new string('-', col1 * -1),col1}|" +
$"{new string('-', col2 * -1),col2}|" +
$"{new string('-', col3 * -1),col3}");
//rows
foreach (JObject contact in collection)
{
Console.WriteLine($"\t|{contact["fullname"],col1}|" +
$"{contact["jobtitle"],col2}|" +
$"{contact["[email protected]"],col3}");
}
}

/// <summary> Displays formatted entity collections to the console. </summary>


/// <param name="label">Descriptive text output before collection contents </param>
/// <param name="collection"> JObject containing array of entities to output by property </param>
/// <param name="properties"> Array of properties within each entity to output. </param>
private static void DisplayFormattedEntities(string label, JArray entities, string[] properties)
{
Console.Write(label);
int lineNum = 0;
foreach (JObject entity in entities)
{
lineNum++;
List<string> propsOutput = new List<string>();
//Iterate through each requested property and output either formatted value if one
//exists, otherwise output plain value.
foreach (string prop in properties)
{
string propValue;
string formattedProp = prop + "@OData.Community.Display.V1.FormattedValue";
if (null != entity[formattedProp])
{
propValue = entity[formattedProp].ToString();
}
else
else
{
if (null != entity[prop])
{
propValue = entity[prop].ToString();
}
else
{
propValue = "NULL";
}
}
propsOutput.Add(propValue);
}
Console.Write("\n\t{0}) {1}", lineNum, string.Join(", ", propsOutput));
}
Console.Write("\n");
}

private static void DisplayExpandedValuesFromTask(JToken collection) {

//Display column widths for task Lookup Values Table


const int col1 = -30;
const int col2 = -30;
const int col3 = -25;
const int col4 = -20;

Console.WriteLine($"\t|{"Subject",col1}|" +
$"{"Contact",col2}|" +
$"{"Account",col3}|" +
$"{"Account CreatedBy",col4}");
Console.WriteLine($"\t|{new string('-', col1 * -1),col1}|" +
$"{new string('-', col2 * -1),col2}|" +
$"{new string('-', col3 * -1),col3}|" +
$"{new string('-', col4 * -1),col4}");
//rows
foreach (JObject task in collection)
{
Console.WriteLine($"\t|{task["subject"],col1}|" +
$"{task["regardingobjectid_contact_task"]["fullname"],col2}|" +
$"{task["regardingobjectid_contact_task"]["parentcustomerid_account"]["name"],col3}|" +
$"{task["regardingobjectid_contact_task"]["parentcustomerid_account"]["createdby"]
["fullname"],col4}");

//Console.WriteLine($"\n\tSubject: " +
// $"{task["subject"]}");
//Console.WriteLine($"\t\tContact: " +
// $"{task["regardingobjectid_contact_task"]["fullname"]}");
//Console.WriteLine($"\t\t\tAccount: " +
// $"{task["regardingobjectid_contact_task"]["parentcustomerid_account"]["name"]}");
//Console.WriteLine($"\t\t\t\tAccount Created by: " +
// $"{task["regardingobjectid_contact_task"]["parentcustomerid_account"]["createdby"]
["fullname"]}");

}
}
}
}

See also
Query Data using the Web API
Web API Query Data Sample
Use the Dataverse Web API
Conditional Operations sample (C#)
7/19/2021 • 5 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample shows how to perform conditional message operations when accessing table rows (entity records)
of the Microsoft Dataverse. The sample uses the Dataverse Web API and the CDSWebApiService class.
Messages using a conditional statement, such as "If-None-Match", in the message header are sent to Dataverse.

NOTE
This sample implements the Dataverse operations and console output detailed in Web API Conditional Operations Sample
and uses the methods available in the CDSWebApiService class for message processing, performance enhancements, and
error management.

Prerequisites
The following is required to build and run the sample:
Microsoft Visual Studio 2019.
Access to Dataverse with privileges to perform the operations described above.

How to run this sample


1. Go to the PowerApps-Samples repository and either clone or download the compressed samples
repository. If you downloaded the compressed file, extract its contents into a local folder.
2. Navigate to the cds/webapi/C#/ConditionalOperations folder and load the solution file into Visual Studio.
3. Edit the App.config file that is shared by several of the samples and set appropriate values for the
Dataverse environment you intend to use: connectionString Url , UserPrincipalName , and Password . You
only need to perform this step once for all samples that share this file.
4. Press F5 to build and run the program in debug mode.

Code listing
This sample depends on the assembly built from in the CDSWebApiService project. For information on the
methods this class provides see CDSWebApiService class.
The following is the code from the Program.cs file:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
namespace PowerApps.Samples
{
/// <summary>
/// This program demonstrates use of conditional operations with the
/// Dataverse Web API.
/// </summary>
class Program
{
//Get environment configuration data from the connection string in the App.config file.
static readonly string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;
static readonly ServiceConfig config = new ServiceConfig(connectionString);

static void Main()


{
// Save the URIs for entity records created in this sample. so they
// can be deleted later.
List<Uri> entityUris = new List<Uri>();

try
{
// Use the wrapper class that handles message processing, error handling, and more.
using (CDSWebApiService svc = new CDSWebApiService(config))
{
Console.WriteLine("--Starting conditional operations demonstration--\n");

#region Create required records


// Create an account record
var account1 = new JObject {
{ "name", "Contoso Ltd" },
{ "telephone1", "555-0000" }, //Phone number value increments with each update
attempt
{ "revenue", 5000000},
{ "description", "Parent company of Contoso Pharmaceuticals, etc."} };

Uri account1Uri = svc.PostCreate("accounts", account1);


entityUris.Add(account1Uri); // Track any created records

// Retrieve the account record that was just created.


string queryOptions = "?$select=name,revenue,telephone1,description";
var retrievedaccount1 = svc.Get(account1Uri.ToString() + queryOptions);

// Store the ETag value from the retrieved record


string initialAcctETagVal = retrievedaccount1["@odata.etag"].ToString();

Console.WriteLine("Created and retrieved the initial account, shown below:");


Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
#endregion Create required records

#region Conditional GET


Console.WriteLine("\n** Conditional GET demonstration **");

// Attempt to retrieve the account record using a conditional GET defined by a message
header with
// the current ETag value.
try
{
retrievedaccount1 = svc.Get(
path: account1Uri.ToString() + queryOptions,
headers: new Dictionary<string, List<string>> {
{ "If-None-Match", new List<string> {initialAcctETagVal}}}
);

// Not expected; the returned response contains content.


Console.WriteLine("Instance retrieved using ETag: {0}", initialAcctETagVal);
Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
}
catch (AggregateException ae) // Message was not successful
{
{
ae.Handle((x) =>
{
if (x is ServiceException) // This we know how to handle.
{
var e = x as ServiceException;
if (e.StatusCode == (int)HttpStatusCode.NotModified) // Expected result
{
Console.WriteLine("Account record retrieved using ETag: {0}",
initialAcctETagVal);
Console.WriteLine("Expected outcome: Entity was not modified so nothing
was returned.");
return true;
}
}
return false; // Let anything else stop the application.
});
}

// Modify the account instance by updating the telephone1 attribute


svc.Put(account1Uri, "telephone1", "555-0001");
Console.WriteLine("\n\bAccount telephone number updated to '555-0001'.\n");

// Re-attempt to retrieve using conditional GET defined by a message header with


// the current ETag value.
try
{
retrievedaccount1 = svc.Get(
path: account1Uri.ToString() + queryOptions,
headers: new Dictionary<string, List<string>> {
{ "If-None-Match", new List<string> {initialAcctETagVal}}}
);

// Expected result
Console.WriteLine("Modified account record retrieved using ETag: {0}",
initialAcctETagVal);
Console.WriteLine("Notice the update ETag value and telephone number");
}
catch (ServiceException e)
{
if (e.StatusCode == (int)HttpStatusCode.NotModified) // Not expected
{
Console.WriteLine("Unexpected outcome: Entity was modified so something should
be returned.");
}
else { throw e; }
}

// Save the updated ETag value


var updatedAcctETagVal = retrievedaccount1["@odata.etag"].ToString();

// Display ty updated record


Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
#endregion Conditional GET

#region Optimistic concurrency on delete and update


Console.WriteLine("\n** Optimistic concurrency demonstration **");

// Attempt to delete original account (if matches original ETag value).


// If you replace "initialAcctETagVal" with "updatedAcctETagVal", the delete will
// succeed. However, we want the delete to fail for now to demonstrate use of the ETag.
Console.WriteLine("Attempting to delete the account using the original ETag value");

try
{
svc.Delete(
uri: account1Uri,
headers: new Dictionary<string, List<string>> {
{ "If-Match", new List<string> {initialAcctETagVal}}}
);

// Not expected; this code should not execute.


Console.WriteLine("Account deleted");
}
catch (ServiceException e)
{
if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Expected result
{
Console.WriteLine("Expected Error: The version of the account record no" +
" longer matches the original ETag.");
Console.WriteLine("\tAccount not deleted using ETag '{0}', status code: '{1}'.",
initialAcctETagVal, e.StatusCode);
}
else { throw e; }
}

Console.WriteLine("Attempting to update the account using the original ETag value");


JObject accountUpdate = new JObject() {
{ "telephone1", "555-0002" },
{ "revenue", 6000000 }
};

try
{
svc.Patch(
uri: account1Uri,
body: accountUpdate,
headers: new Dictionary<string, List<string>> {
{ "If-Match", new List<string> {initialAcctETagVal}}}
);

// Not expected; this code should not execute.


Console.WriteLine("Account updated using original ETag {0}", initialAcctETagVal);
}
catch (ServiceException e)
{
if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Expected error
{
Console.WriteLine("Expected Error: The version of the existing record doesn't "
+ "match the ETag property provided.");
Console.WriteLine("\tAccount not updated using ETag '{0}', status code: '{1}'.",
initialAcctETagVal, (int)e.StatusCode);
}
else { throw e; }
}

// Reattempt update if matches current ETag value.


accountUpdate["telephone1"] = "555-0003";
Console.WriteLine("Attempting to update the account using the current ETag value");
try
{
svc.Patch(
uri: account1Uri,
body: accountUpdate,
headers: new Dictionary<string, List<string>> {
{ "If-Match", new List<string> { updatedAcctETagVal }} }
);

// Expected program flow; this code should execute.


Console.WriteLine("\nAccount successfully updated using ETag: {0}.",
updatedAcctETagVal);
}
catch (ServiceException e)
{
if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Not expected
{
Console.WriteLine("Unexpected status code: '{0}'", (int)e.StatusCode);
}
else { throw e; }
}

// Retrieve and output current account state.


retrievedaccount1 = svc.Get(account1Uri.ToString() + queryOptions);

Console.WriteLine("\nBelow is the final state of the account");


Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
#endregion Optimistic concurrency on delete and update

#region Delete created records

// Delete (or keep) all the created entity records.


Console.Write("\nDo you want these entity records deleted? (y/n) [y]: ");
String answer = Console.ReadLine().Trim();

if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty))


entityUris.Clear();

foreach (Uri entityUrl in entityUris) svc.Delete(entityUrl);

#endregion Delete created records


}
}
catch (ServiceException e)
{
Console.WriteLine("Message send response: status code {0}, {1}",
e.StatusCode, e.ReasonPhrase);
}
}
}
}

See also
Perform conditional operations using the Web API
Web API Conditional Operations Sample
Use the Dataverse Web API
Web API Functions and Actions Sample (C#)
7/19/2021 • 7 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to call bound and unbound functions and actions, including custom actions,
using the Microsoft Dataverse Web API.

NOTE
This sample implements the operations detailed in the Web API Functions and Actions Sample. This sample also uses the
CDSWebApiService class methods for messaging, performance enhancements, and error processing.

Prerequisites
Prerequisites for all Dataverse Web API C# samples are detailed in the Prerequisites section of the parent topic
Web API Samples (C#).

How to run this sample


Go to the PowerApps-Samples GitHub repository, clone or download the samples repository, and extract its
contents into a local folder. Navigate to the cds/webapi/C#/FunctionsAndActions folder. This folder should
contain the following files:

F IL E P URP O SE/ DESC RIP T IO N

Program.cs Contains the primary source code for this sample.

App.config The application configuration file, which contains placeholder


Dataverse server connection information. This file is shared
with several of the Web API samples in the repo. If you
configure connection information for one sample, you can
run the other samples with the same configuration.

FunctionsAndActions.sln The standard Visual Studio 2019 solution, project, NuGet


FunctionsAndActions.csproj package configuration, and assembly information files for
Packages.config this sample.
AssemblyInfo.cs

WebAPIFunctionsandActions_1_0_0_0_managed.zip A custom managed solution containing two custom actions


called by this sample.

Next, use the following procedure to run this sample.


1. Using Dynamics 365 Customer Engagement (Settings > Solutions ) or Power Apps (Solutions ), import
the provided solution (.zip) file into your testing environment.
2. Locate and double-click on the solution file, FunctionsAndActions.sln, to load the solution into Visual
Studio. Build the FunctionsAndActions solution. This should automatically download and install all the
required NuGet packages that are either missing or need to be updated.
3. Edit the application configuration file, App.config, to specify connection information for your Dataverse
server.
4. Build and run the FunctionsAndActions project from within Visual Studio. All sample solutions are
configured to run in debug mode by default.

Code listing
Program.cs partial listing

sing System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;

namespace PowerApps.Samples
{
/// <summary>
/// This program demonstrates use of functions and actions with the
/// Power Platform data service Web API.
/// </summary>
/// <remarks>Be sure to fill out App.config with your test environment information
/// and import the provided managed solution into your test environment using the web app
/// before running this program.</remarks>
/// <see cref="Https://docs.microsoft.com/powerapps/developer/data-platform/webapi/samples/functions-
actions-csharp"/>
public class Program
{
public static void Main()
{
Console.Title = "Function and Actions demonstration";

// Track entity instance URIs so those records can be deleted prior to exit.
Dictionary<string, Uri> entityUris = new Dictionary<string, Uri>();

try
{
// Get environment configuration information from the connection string in App.config.
ServiceConfig config = new ServiceConfig(
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString);

// Use the service class that handles HTTP messaging, error handling, and
// performance optimizations.
using (CDSWebApiService svc = new CDSWebApiService(config))
{
// Create any entity instances required by the program code that follows
CreateRequiredRecords(svc, entityUris);

#region Call an unbound function with no parameters


Console.WriteLine("\n* Call an unbound function with no parameters.");

// Retrieve the current user's full name from the WhoAmI function:
Console.Write("\tGetting information on the current user..");
JToken currentUser = svc.Get("WhoAmI");

// Obtain the user's ID and full name


JToken user = svc.Get("systemusers(" + currentUser["UserId"] + ")?$select=fullname");

Console.WriteLine("completed.");
Console.WriteLine("\tCurrent user's full name is '{0}'.", user["fullname"]);
#endregion Call an unbound function with no parameters

#region Call an unbound function that requires parameters


Console.WriteLine("\n* Call an unbound function that requires parameters");
// Retrieve the code for the specified time zone
int localeID = 1033;
string timeZoneName = "Pacific Standard Time";

// Define the unbound function and its parameters


string[] uria = new string[] {
"GetTimeZoneCodeByLocalizedName",
"(LocalizedStandardName=@p1,LocaleId=@p2)",
"?@p1='" + timeZoneName + "'&@p2=" + localeID };

// This would also work:


// string[] uria = ["GetTimeZoneCodeByLocalizedName", "(LocalizedStandardName='" +
// timeZoneName + "',LocaleId=" + localeId + ")"];

JToken localizedName = svc.Get(string.Join("", uria));


string timeZoneCode = localizedName["TimeZoneCode"].ToString();

Console.WriteLine(
"\tThe time zone '{0}' has the code '{1}'.", timeZoneName, timeZoneCode);
#endregion Call an unbound function that requires parameters

#region Call a bound function


Console.WriteLine("\n* Call a bound function");

// Retrieve the total time (minutes) spent on all tasks associated with
// incident "Sample Case".
string boundUri = entityUris["Sample Case"] +
@"/Microsoft.Dynamics.CRM.CalculateTotalTimeIncident()";

JToken cttir = svc.Get(boundUri);


string totalTime = cttir["TotalTime"].ToString();

Console.WriteLine("\tThe total duration of tasks associated with the incident " +


"is {0} minutes.", totalTime);
#endregion Call a bound function

#region Call an unbound action that requires parameters


Console.WriteLine("\n* Call an unbound action that requires parameters");

// Close the existing opportunity "Opportunity to win" and mark it as won.


JObject opportClose = new JObject()
{
{ "subject", "Won Opportunity" },
{ "[email protected]", entityUris["Opportunity to win"] }
};

JObject winOpportParams = new JObject()


{
{ "Status", "3" },
{ "OpportunityClose", opportClose }
};

JObject won = svc.Post("WinOpportunity", winOpportParams);

Console.WriteLine("\tOpportunity won.");
#endregion Call an unbound action that requires parameters

#region Call a bound action that requires parameters


Console.WriteLine("\n* Call a bound action that requires parameters");

// Add a new letter tracking activity to the current user's queue.


// First create a letter tracking instance.
JObject letterAttributes = new JObject()
{
{"subject", "Example letter" },
{"description", "Body of the letter" }
};
Console.Write("\tCreating letter 'Example letter'..");

Uri letterUri = svc.PostCreate("letters", letterAttributes);


entityUris.Add("Example letter", letterUri);

Console.WriteLine("completed.");

//Retrieve the ID associated with this new letter tracking activity.


JToken letter = svc.Get(letterUri + "?$select=activityid,subject");
string letterActivityId = (string)letter["activityid"];

// Retrieve the URL to current user's queue.


string myUserId = (string)currentUser["UserId"];

JToken queueRef = svc.Get("systemusers(" + myUserId + ")/queueid/$ref");


string myQueueUri = (string)queueRef["@odata.id"];

//Add the letter activity to current user's queue, then return its queue ID.
JObject targetUri = JObject.Parse(
@"{activityid: '" + letterActivityId + @"', '@odata.type':
'Microsoft.Dynamics.CRM.letter' }");

JObject addToQueueParams = new JObject()


{
{ "Target", targetUri }
};

string queueItemId = (string)svc.Post(


myQueueUri + "/Microsoft.Dynamics.CRM.AddToQueue", addToQueueParams)["QueueItemId"];

Console.WriteLine("\tLetter 'Example letter' added to current user's queue.");


Console.WriteLine("\tQueueItemId returned from AddToQueue action: {0}", queueItemId);
#endregion Call a bound action that requires parameters

#region Call a bound custom action that requires parameters


Console.WriteLine("\n* Call a bound custom action that requires parameters");

// Add a note to a specified contact. Uses the custom action sample_AddNoteToContact,


which
// is bound to the contact to annotate, and takes a single param, the note to add. It
also
// returns the URI to the new annotation.

JObject note = JObject.Parse(


@"{NoteTitle: 'Sample note', NoteText: 'The text content of the note.'}");
string actionUri = entityUris["Jon Fogg"].ToString() +
"/Microsoft.Dynamics.CRM.sample_AddNoteToContact";

JObject contact = svc.Post(actionUri, note);


Uri annotationUri = new Uri(svc.BaseAddress + "annotations(" + contact["annotationid"] +
")");
entityUris.Add((string)note["NoteTitle"], annotationUri);

Console.WriteLine("\tA note with the title '{0}' was created and " +
"associated with the contact 'Jon Fogg'.", note["NoteTitle"]);
#endregion Call a bound custom action that requires parameters

#region Call an unbound custom action that requires parameters


Console.WriteLine("\n* Call an unbound custom action that requires parameters");

// Create a customer of the specified type, using the custom action


sample_CreateCustomer,
// which takes two parameters: the type of customer ('account' or 'contact') and the
name of
// the new customer.
string customerName = "New account customer (sample)";
JObject customerAttributes = JObject.Parse(
@"{CustomerType: 'account', AccountName: '" + customerName + "'}");
JObject response = svc.Post("sample_CreateCustomer", customerAttributes);
Console.WriteLine("\tThe account '" + customerName + "' was created.");

// Because the CreateCustomer custom action does not return any data about the created
instance,
// we must query the customer instance to figure out its URI.
JToken customer = svc.Get("accounts?$filter=name eq 'New account customer
(sample)'&$select=accountid&$top=1");
Uri customerUri = new Uri(svc.BaseAddress + "accounts(" + customer["value"][0]
["accountid"] + ")");
entityUris.Add( customerName, customerUri );

// Try to call the same custom action with invalid parameters, here the same name is
// not valid for a contact. (ContactFirstname and ContactLastName parameters are
// required when CustomerType is contact.
customerAttributes = JObject.Parse(
@"{CustomerType: 'contact', AccountName: '" + customerName + "'}");

try
{
customerUri = svc.PostCreate("sample_CreateCustomer", customerAttributes);
Console.WriteLine("\tCall to the custom CreateCustomer action succeeded, which was
not expected.");
}
catch (AggregateException e)
{
Console.WriteLine("\tCall to the custom CreateCustomer action did not succeed (as
was expected).");
foreach (Exception inner in (e as AggregateException).InnerExceptions)
{ Console.WriteLine("\t -" + inner.Message); }
}
#endregion Call an unbound custom action that requires parameters

DeleteEntityRecords(svc, entityUris);
}
}
catch (Exception e)
{
Console.BackgroundColor = ConsoleColor.Red; // Highlight exceptions
if ( e is AggregateException )
{
foreach (Exception inner in (e as AggregateException).InnerExceptions)
{ Console.WriteLine("\n" + inner.Message); }
}
else if ( e is ServiceException)
{
var ex = e as ServiceException;
Console.WriteLine("\nMessage send response: status code {0}, {1}",
ex.StatusCode, ex.ReasonPhrase);
}
Console.ReadKey(); // Pause terminal
}
}
}
}

See also
Use the Dataverse Web API
Use Web API functions
Use Web API actions
Web API Samples
Web API Functions and Actions Sample
Web API CDSWebApiService Parallel Operations
Sample (C#)
7/19/2021 • 4 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This example shows how to use a System.Threading.Tasks.Parallel.ForEach Method loop to enable data
parallelism over a set of table rows (entity records) to create in Dataverse.
This sample uses the CDSWebApiService class synchronous methods within operations. Because the
CDSWebApiService class can manage Service Protection API limits, this code can be resilient to the transient
429 errors that clients should expect. It will retry a configurable number of times.
More information:
Web API CDSWebApiService class Sample (C#)
Service Protection API Limits
This sample is based on the How to: Write a simple Parallel.ForEach loop example, but modified to perform
create and delete operations with Dataverse entities using the synchronous methods provided by the
CDSWebApiService class.

NOTE
If you want to use Fiddler to observe the expected service protection API limits, you will need to set the number of rows
to create to be around 10,000. They will start to appear after 5 minutes. Note how the application retries the failures and
completes the flow of all the rows.

Prerequisites
The following is required to build and run the CDSWebApiService C# samples :
Microsoft Visual Studio 2019.
Access to Microsoft Dataverse with privileges to perform CRUD operations.

How to run this sample


1. Go to the PowerApps-Samples GitHub repository, clone or download the samples repository, and extract
its contents into a local folder.
2. Navigate to the repository folder cds/webapi/C#/ParallelOperations, and then open the
ParallelOperations.sln file in Visual Studio.
3. In Solution Explorer , under the ParallelOperations project, open the App.config file. This is a shared
application configuration file used by all the Web API C# samples. Once you edit this file, you do not have
to edit it again unless you're changing the environment or logon used to run the samples.
4. You must edit the Url , UserPrincipalName , and Password values to set the Dataverse instance and
credentials you want to connect to.

<add name="Connect"
connectionString="Url=https://yourorg.api.crm.dynamics.com;
Authority=null;
ClientId=51f81489-12ee-4a9e-aaae-a2591f45987d;
RedirectUrl=app://58145B91-0C36-4500-8554-080854F2AC97;
[email protected];
Password=y0urp455w0rd;
CallerObjectId=null;
Version=9.1;
MaxRetries=3;
TimeoutInSeconds=180;
"/>

5. Make sure that the ParallelOperations project is set as the startup project. The name of the project
should be bold to indicate it is the startup project. If the name is not bold, right-click it in the solution
explorer and select Set as Star tup Project .
6. Press F5 to run the program in debug mode.

Code listing
This sample depends on the assembly included in the CDSWebAPIService project. For information on the
methods this class provides see: Web API CDSWebApiService class Sample (C#).
The following is the code from the Program.cs file:

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;

namespace PowerApps.Samples
{
internal class Program
{
//Get configuration data from App.config connectionStrings
private static readonly string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;

private static readonly ServiceConfig serviceConfig = new ServiceConfig(connectionString);

//Controls the max degree of parallelism


private static readonly int maxDegreeOfParallelism = 10;

//How many records to create with this sample.


private static readonly int numberOfRecords = 100;

private static void Main()


{
#region Optimize Connection

//Change max connections from .NET to a remote service default: 2


System.Net.ServicePointManager.DefaultConnectionLimit = 65000;
//Bump up the min threads reserved for this app to ramp connections faster - minWorkerThreads
defaults to 4, minIOCP defaults to 4
System.Threading.ThreadPool.SetMinThreads(100, 100);
//Turn off the Expect 100 to continue message - 'true' will cause the caller to wait until it
round-trip confirms a connection to the server
System.Net.ServicePointManager.Expect100Continue = false;
//Can decreas overall transmission overhead but can cause delay in data packet arrival
System.Net.ServicePointManager.UseNagleAlgorithm = false;

#endregion Optimize Connection

var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism };

var count = 0;

//Will be populated with account records to import


List<JObject> accountsToImport = new List<JObject>();
//ConcurrentBag is a thread-safe, unordered collection of objects.
ConcurrentBag<Uri> accountsToDelete = new ConcurrentBag<Uri>();
Console.WriteLine($"Preparing to create {numberOfRecords} acccount records using Web API.");

//Add account records to the list to import


while (count < numberOfRecords)
{
var account = new JObject
{
["name"] = $"Account {count}"
};
accountsToImport.Add(account);
count++;
}

try
{
using (CDSWebApiService svc = new CDSWebApiService(serviceConfig))
{
Console.WriteLine($"Creating {accountsToImport.Count} accounts");
var startCreate = DateTime.Now;

//Create the accounts in parallel


Parallel.ForEach(accountsToImport, parallelOptions, (account) =>

{
//Add the Uri returned to the ConcurrentBag to delete later
accountsToDelete.Add(svc.PostCreate("accounts", account));
});

//Calculate the duration to complete


var secondsToCreate = (DateTime.Now - startCreate).TotalSeconds;

Console.WriteLine($"Created {accountsToImport.Count} accounts in


{Math.Round(secondsToCreate)} seconds.");

Console.WriteLine($"Deleting {accountsToDelete.Count} accounts");


var startDelete = DateTime.Now;

//Delete the accounts in parallel


Parallel.ForEach(accountsToDelete, parallelOptions, (uri) =>
{
svc.Delete(uri);
});

//Calculate the duration to complete


var secondsToDelete = (DateTime.Now - startDelete).TotalSeconds;

Console.WriteLine($"Deleted {accountsToDelete.Count} accounts in


{Math.Round(secondsToDelete)} seconds.");
Console.WriteLine("Sample completed. Press any key to exit.");
Console.ReadLine();
}
}
catch (Exception)
{
throw;
}
}
}
}
}

See also
Use the Dataverse Web API
Web API CDSWebApiService class Sample (C#)
Web API CDSWebApiService Async Parallel Operations Sample (C#)
Web API CDSWebApiService Basic Operations Sample (C#)
Create a table row using the Web API
Update and delete table rows using the Web API
Web API CDSWebApiService Async Parallel
Operations Sample (C#)
7/19/2021 • 3 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates using Task Parallel Library (TPL) dataflow components Dataflow (Task Parallel Library)
with asynchronous requests.
TPL provides capabilities to add parallelism and concurrency to applications. These capabilities are an important
part of maximizing throughput when performing operations that will add or update data within Dataverse.
This sample uses the CDSWebApiService class asynchronous methods within asynchronous operations. Because
the CDSWebApiService class can manage Service Protection API limits, this code can be resilient to the transient
429 errors that clients should expect. It will retry a configurable number of times. More information: Service
Protection API Limits
This sample simply creates a configurable number of account rows (records) to create, which it will in turn
delete. This sample uses dataflow components to process the rows and transform the results of the create
operation into the next phase that deletes these rows. Because of the nature of this data flow, delete operations
for previously created rows will start before all the rows to create are finished.

Prerequisites
The following is required to build and run the CDSWebApiService C# samples :
Microsoft Visual Studio 2019.
Access to Microsoft Dataverse with privileges to perform CRUD operations.

How to run this sample


1. Go to the PowerApps-Samples GitHub repository, clone or download the samples repository, and extract
its contents into a local folder.
2. Navigate to the repository folder cds/webapi/C#/AsyncParallelOperations, and then open the
AsyncParallelOperations.sln file in Visual Studio.
3. In Solution Explorer , under the AsyncParallelOperations project, open the App.config file. This is a
shared application configuration file used by all the Web API C# samples. Once you edit this file, you do
not have to edit it again unless you're changing the environment or logon used to run the samples.
4. You must edit the Url , UserPrincipalName , and Password values to set the Dataverse instance and
credentials you want to connect to.
<add name="Connect"
connectionString="Url=https://yourorg.api.crm.dynamics.com;
Authority=null;
ClientId=51f81489-12ee-4a9e-aaae-a2591f45987d;
RedirectUrl=app://58145B91-0C36-4500-8554-080854F2AC97;
[email protected];
Password=y0urp455w0rd;
CallerObjectId=null;
Version=9.1;
MaxRetries=3;
TimeoutInSeconds=180;
"/>

5. Make sure that the AsyncParallelOperations project is set as the startup project. The name of the
project should be bold to indicate it is the startup project. If the name is not bold, right-click it in the
solution explorer and select Set as Star tup Project .
6. Press F5 to run the program in debug mode.

Code listing
This sample depends on the assembly included in the CDSWebAPIService project. For information on the
methods this class provides see: Web API CDSWebApiService class Sample (C#).
The following is the code from the Program.cs file:

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace PowerApps.Samples
{
internal class Program
{
//Get configuration data from App.config connectionStrings
private static readonly string connectionString =
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;

private static readonly ServiceConfig serviceConfig = new ServiceConfig(connectionString);

//Controls the max degree of parallelism


private static readonly int maxDegreeOfParallelism = 10;

//How many records to create with this sample.


private static readonly int numberOfRecords = 100;

private static async Task Main()


{
#region Optimize Connection

//Change max connections from .NET to a remote service default: 2


System.Net.ServicePointManager.DefaultConnectionLimit = 65000;
//Bump up the min threads reserved for this app to ramp connections faster - minWorkerThreads
defaults to 4, minIOCP defaults to 4
System.Threading.ThreadPool.SetMinThreads(100, 100);
//Turn off the Expect 100 to continue message - 'true' will cause the caller to wait until it
round-trip confirms a connection to the server
System.Net.ServicePointManager.Expect100Continue = false;
//Can decrease overall transmission overhead but can cause delay in data packet arrival
System.Net.ServicePointManager.UseNagleAlgorithm = false;
#endregion Optimize Connection

var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions


{
MaxDegreeOfParallelism = maxDegreeOfParallelism
};

var count = 0;
double secondsToComplete;

//Will be populated with account records to import


List<JObject> accountsToImport = new List<JObject>();

Console.WriteLine($"Preparing to create {numberOfRecords} acccount records using Web API.");

//Add account records to the list to import


while (count < numberOfRecords)
{
var account = new JObject
{
["name"] = $"Account {count}"
};
accountsToImport.Add(account);
count++;
}

using (var svc = new CDSWebApiService(serviceConfig))


{
secondsToComplete = await ProcessData(svc, accountsToImport, executionDataflowBlockOptions);
}

Console.WriteLine($"Created and deleted {accountsToImport.Count} accounts in


{Math.Round(secondsToComplete)} seconds.");

Console.WriteLine("Sample completed. Press any key to exit.");


Console.ReadLine();
}

private static async Task<double> ProcessData(CDSWebApiService svc, List<JObject> accountsToImport,


ExecutionDataflowBlockOptions executionDataflowBlockOptions)
{
var createAccounts = new TransformBlock<JObject, Uri>(
async a =>
{
return await svc.PostCreateAsync("accounts", a);
},
executionDataflowBlockOptions
);

var deleteAccounts = new ActionBlock<Uri>(


async u =>
{
await svc.DeleteAsync(u);
},
executionDataflowBlockOptions
);

createAccounts.LinkTo(deleteAccounts, new DataflowLinkOptions { PropagateCompletion = true });

var start = DateTime.Now;

accountsToImport.ForEach(a => createAccounts.SendAsync(a));


createAccounts.Complete();
await deleteAccounts.Completion;

//Calculate the duration to complete


return (DateTime.Now - start).TotalSeconds;
}
}
}
}

See also
Use the Dataverse Web API
Web API CDSWebApiService class Sample (C#)
Web API CDSWebApiService Basic Operations Sample (C#)
Web API CDSWebApiService Parallel Operations Sample (C#)
Create a table using the Web API
Update and delete table rows using the Web API
Global Discovery Service Sample (C#)
7/19/2021 • 2 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample shows how to access the global Discovery Service using the Web API.

How to run this sample


The sample source code is available on Github at PowerApps-Samples/cds/webapi/C#/GlobalDiscovery.
To run the sample:
1. Download or clone the sample so that you have a local copy.
2. Open the project solution file in Visual Studio.
3. Press F5 to build and run the sample.

What this sample does


When run, the sample opens a logon form where you must specify the Microsoft Dataverse credentials for an
enabled user. The program then displays to the console window a list of Dataverse environment instances that
the specified user is a member of.
Other important aspects of this sample include:
1. Handles breaking API changes in different versions of Azure Active Directory Authentication Library (ADAL)
2. No dependency on helper code or a helper library since all required code, including authentication code, is
provided.

How this sample works


This sample uses an HttpClient to authenticate the specified user using ADAL, and then calls the global
Discovery Service to return information about available Dataverse environment instances the user is a member
of.
Demonstrates
The sample depends on the GetInstances method and the Instance class below:
/// <summary>
/// Uses the global discovery service to return environment instances
/// </summary>
/// <param name="username">The user name</param>
/// <param name="password">The password</param>
/// <returns>A list of Instances</returns>
static List<Instance> GetInstances(string username, string password)
{

string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";


HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GetAccessToken(username, password,
new Uri("https://disco.crm.dynamics.com/api/discovery/")));
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri(GlobalDiscoUrl);

HttpResponseMessage response =
client.GetAsync("api/discovery/v2.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;

if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
string result = response.Content.ReadAsStringAsync().Result;
JObject body = JObject.Parse(result);
JArray values = (JArray)body.GetValue("value");

if (!values.HasValues)
{
return new List<Instance>();
}

return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
}
else
{
throw new Exception(response.ReasonPhrase);
}
}

/// <summary>
/// Object returned from the Discovery Service.
/// </summary>
class Instance
{
public string Id { get; set; }
public string UniqueName { get; set; }
public string UrlName { get; set; }
public string FriendlyName { get; set; }
public int State { get; set; }
public string Version { get; set; }
public string Url { get; set; }
public string ApiUrl { get; set; }
public DateTime LastUpdated { get; set; }
}

See Also
Discover the URL for your organization
Web API Data operations Samples (Client-side
JavaScript)
7/19/2021 • 5 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This article provides common understanding about Web API samples using client-side JavaScript. While each
sample focuses on a different aspect of Microsoft Dataverse Web API, they all follow similar process and
structure described in this topic.

Web API Samples using client-side JavaScript


The following samples use the patterns described here:

SA M P L E SA M P L E GRO UP DESC RIP T IO N

Web API Basic Operations Sample Web API Basic Operations Sample Demonstrates how to create, retrieve,
(Client-side JavaScript) update, delete, associate and
disassociate Dataverse table rows
(entity records).

Web API Query Data Sample (Client- Web API Query Data Sample Demonstrates how to use OData v4
side JavaScript) query syntax and functions as well as
Dataverse query functions. Includes
demonstration of working with pre-
defined queries and using FetchXML to
perform queries.

Web API Conditional Operations Web API Conditional Operations Demonstrates how to perform
Sample (Client-side JavaScript) Sample conditional operations. The behavior of
these operations depends on criteria
you specify.

Web API Functions and Actions Web API Functions and Actions Demonstrates how to use bound and
Sample (Client-side JavaScript) Sample unbound functions and actions,
including custom actions.

How to download the source code for the sample.


The source code for each sample is available on GitHub. The link to download each sample is included in the
individual page for that sample.
After you download the sample, extract the compressed file. Find the solution for each sample within the C#
folder because the project is an empty ASP.NET web application project. A Dataverse solution is also provided in
the download that you can import and run.
NOTE
Neither Visual Studio or ASP.NET is required to develop client-side JavaScript for Dataverse. However, Visual Studio does
provide a good experience for writing JavaScript.

How to import the Dataverse solution that contains the sample.


Within each project you will find a Dataverse managed solution file. The name of this file will depend on the
sample's project name, but the file name will end with _managed.zip .
To import the Dataverse solution to your Dataverse server, do the following:
1. Extract the contents of the downloaded zip file and locate the Dataverse solution file, which will also be a
zip file. For example, if you downloaded the Basic Operations sample, look for the Dataverse solution zip
file with the name WebAPIBasicOperations\WebAPIBasicOperations_1_0_0_0_managed.zip .
2. In the Dataverse UI, go to Settings > Solutions . This page lists all solutions on your Dataverse server.
After you finished importing this solution, the solution name for that sample will appear in this list (e.g.:
Web API Basics Operations ).
3. Select Impor t and follow the instructions on the import dialog to complete this action.

How to run the sample to see the script in action


The sample program runs as a web resource within Dataverse. The imported solution provides a configuration
page that gives you an option to keep or delete sample data and a button to start the sample program.
To run the sample, do the following:
1. From the All Solutions page in Dataverse, select the solution name (e.g.: Web API Basics Operations
link). This will open the solution's properties in a new window.
2. From the left navigation menu, select Configuration .
3. Select Star t Sample button to execute the sample code.

Common elements found in each sample


The following list highlights some common elements found in each of these samples.
The Sdk.startSample function is called when a user selects the Star t Sample button from the HTML
page. The Sdk.startSample function initializes global variables and kicks off the first operation in the
chain.
Program output and error messages are sent to the browser’s debugger console. To see these output,
open the console window first before running the sample. Press F12 to access the developer tools,
including the console window, in Microsoft Edge browser.
These samples use the browser native ES6-Promise implementation for modern browsers that support it.
For Internet Explorer, this sample uses the ES6-Promise polyfill because Internet Explorer is the only
browser supported by Dataverse which does not have native support for this feature.
Promises are not required. Similar interactions can be performed using callback functions.
The Sdk.request function handles the request based on the information passed in as parameters.
Depending on the need of each sample, the parameters passed in may be different. See the source code
of that sample for more details.

/**
* @function request
* @description Generic helper function to handle basic XMLHttpRequest calls.
* @param {string} action - The request action. String is case-sensitive.
* @param {string} uri - An absolute or relative URI. Relative URI starts with a "/".
* @param {object} data - An object representing an entity. Required for create and update actions.
* @returns {Promise} - A Promise that returns either the request object or an error object.
*/
Sdk.request = function (action, uri, data) {
if (!RegExp(action, "g").test("POST PATCH PUT GET DELETE")) { // Expected action verbs.
throw new Error("Sdk.request: action parameter must be one of the following: " +
"POST, PATCH, PUT, GET, or DELETE.");
}
if (!typeof uri === "string") {
throw new Error("Sdk.request: uri parameter must be a string.");
}
if ((RegExp(action, "g").test("POST PATCH PUT")) && (data === null || data === undefined)) {
throw new Error("Sdk.request: data parameter must not be null for operations that create or
modify data.");
}

// Construct a fully qualified URI if a relative URI is passed in.


if (uri.charAt(0) === "/") {
uri = clientUrl + webAPIPath + uri;
}

return new Promise(function (resolve, reject) {


var request = new XMLHttpRequest();
request.open(action, encodeURI(uri), true);
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
request.onreadystatechange = function () {
if (this.readyState === 4) {
request.onreadystatechange = null;
switch (this.status) {
case 200: // Success with content returned in response body.
case 204: // Success with no content returned in response body.
resolve(this);
break;
default: // All other statuses are unexpected so are treated like errors.
var error;
try {
error = JSON.parse(request.response).error;
} catch (e) {
error = new Error("Unexpected Error");
}
reject(error);
break;
}

}
};
request.send(JSON.stringify(data));
});
};

The Sdk.request function returns a promise. When the request wrapped by the promise is completed,
the promise is either resolved or rejected. If it is resolved, the function in the following then method will
be called. If it is rejected, the function in the following catch method will be called. If the function within
the then method itself returns a promise, the chain of operations within consecutive then methods can
continue. Returning a promise allows us to chain these sample operations together in a way that is
preferred by many developers to traditional callback functions. For more information about promise, see
JavaScript Promise.
See also
Use the Dataverse Web API
Web API Samples
Web API Samples (C#)
Web API Basic Operations Sample (Client-side
JavaScript)
7/19/2021 • 16 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to perform basic CRUD (create, retrieve, update, and delete) and association and
dissociation operations on tables rows (entity records) using client-side JavaScript.

NOTE
This sample implements the operations detailed in the Web API Basic Operations Sample and uses the common JavaScript
constructs described in Web API Samples (Client-side JavaScript)

Prerequisites
To run this sample, the following is required:
Access to Microsoft Dataverse environment.
A user account with privileges to import solutions and perform CRUD operations, typically a system
administrator or system customizer security role.

Run this sample


To run this sample, download the solution package from here and extract the contents. Locate the
WebAPIBasicOperations_1_0_0_1_managed.zip solution and import it into your Dataverse environment and run the
sample. For instructions on how to import the sample solution, see Web API Samples (Client-side JavaScript).

Code sample
This sample includes two web resources:
WebAPIBasicOperations.html
WebAPIBasicOperations.js

WebAPIBasicOperations.html
The WebAPIBasicOperations.html web resource provides the context in which the JavaScript code will run.
<html>
<head>
<title>Microsoft CRM Web API Basic Operations Example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
<script src="scripts/es6promise.js" type="text/javascript"></script>
<script src="scripts/WebAPIBasicOperations.js" type="text/javascript"></script>

<style type="text/css">
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#preferences {
border: inset;
padding: 10px 10px;
}

#output_area {
border: inset;
background-color: gainsboro;
padding: 10px 10px;
}
</style>
</head>
<body>
<h1>Microsoft CRM Web API Basic Operations Example</h1>
<p>This page demonstrates the CRM Web API's basic operations using JavaScript.</p>

<h2>Instructions</h2>
<p>
Choose your preferences and run the JavaScript code.
Use your browser's developer tools to view the output written to the console (e.g.: in IE 11 or
Microsoft Edge,
press F12 to load the Developer Tools).
</p>
<p>
Remove sample data (Choose whether you want to delete sample data created during this execution):
<br />
<input name="removesampledata" type="radio" value="yes" checked />
Yes
<input name="removesampledata" type="radio" value="no" />
No
</p>
<input type="button" name="start_sample" value="Start Sample" onclick="Sdk.startSample()" />

</body>
</html>

WebAPIBasicOperations.js
The WebAPIBasicOperations.js web resource is the JavaScript library that defines the operations this sample
performs.

"use strict";
var Sdk = window.Sdk || {};

/**
* @function getClientUrl
* @description Get the client URL.
* @returns {string} The client URL.
*/
Sdk.getClientUrl = function () {
Sdk.getClientUrl = function () {
var context;
// GetGlobalContext defined by including reference to
// ClientGlobalContext.js.aspx in the HTML page.
if (typeof GetGlobalContext != "undefined") {
context = GetGlobalContext();
} else {
if (typeof Xrm != "undefined") {
// Xrm.Page.context defined within the Xrm.Page object model for form scripts.
context = Xrm.Page.context;
} else {
throw new Error("Context is not available.");
}
}
return context.getClientUrl();

};

/**
* An object instantiated to manage detecting the
* Web API version in conjunction with the
* Sdk.retrieveVersion function
*/
Sdk.versionManager = new function () {
//Start with base version
var _webAPIMajorVersion = 8;
var _webAPIMinorVersion = 0;
//Use properties to increment version and provide WebAPIPath string used by Sdk.request;
Object.defineProperties(this, {
"WebAPIMajorVersion": {
get: function () {
return _webAPIMajorVersion;
},
set: function (value) {
if (typeof value != "number") {
throw new Error("Sdk.versionManager.WebAPIMajorVersion property must be a number.")
}
_webAPIMajorVersion = parseInt(value, 10);
}
},
"WebAPIMinorVersion": {
get: function () {
return _webAPIMinorVersion;
},
set: function (value) {
if (isNaN(value)) {
throw new Error("Sdk.versionManager._webAPIMinorVersion property must be a number.")
}
_webAPIMinorVersion = parseInt(value, 10);
}
},
"WebAPIPath": {
get: function () {
return "/api/data/v" + _webAPIMajorVersion + "." + _webAPIMinorVersion;
}
}
})

//Setting variables specific to this sample within a container so they won't be


// overwritten by another scripts code
Sdk.SampleVariables = {
entitiesToDelete: [], // Entity URIs to be deleted later (if user so chooses)
deleteData: true, // Controls whether sample data are deleted at the end of sample run
contact1Uri: null, // e.g.: Peter Cambel
contactAltUri: null, // e.g.: Peter_Alt Cambel
account1Uri: null, // e.g.: Contoso, Ltd
account2Uri: null, // e.g.: Fourth Coffee
contact2Uri: null, // e.g.: Susie Curtis
contact2Uri: null, // e.g.: Susie Curtis
opportunity1Uri: null, // e.g.: Adventure Works
competitor1Uri: null

/**
* @function request
* @description Generic helper function to handle basic XMLHttpRequest calls.
* @param {string} action - The request action. String is case-sensitive.
* @param {string} uri - An absolute or relative URI. Relative URI starts with a "/".
* @param {object} data - An object representing an entity. Required for create and update actions.
* @param {object} addHeader - An object with header and value properties to add to the request
* @returns {Promise} - A Promise that returns either the request object or an error object.
*/
Sdk.request = function (action, uri, data, addHeader) {
if (!RegExp(action, "g").test("POST PATCH PUT GET DELETE")) { // Expected action verbs.
throw new Error("Sdk.request: action parameter must be one of the following: " +
"POST, PATCH, PUT, GET, or DELETE.");
}
if (!typeof uri === "string") {
throw new Error("Sdk.request: uri parameter must be a string.");
}
if ((RegExp(action, "g").test("POST PATCH PUT")) && (!data)) {
throw new Error("Sdk.request: data parameter must not be null for operations that create or modify
data.");
}
if (addHeader) {
if (typeof addHeader.header != "string" || typeof addHeader.value != "string") {
throw new Error("Sdk.request: addHeader parameter must have header and value properties that are
strings.");
}
}

// Construct a fully qualified URI if a relative URI is passed in.


if (uri.charAt(0) === "/") {
//This sample will try to use the latest version of the web API as detected by the
// Sdk.retrieveVersion function.
uri = Sdk.getClientUrl() + Sdk.versionManager.WebAPIPath + uri;
}

return new Promise(function (resolve, reject) {


var request = new XMLHttpRequest();
request.open(action, encodeURI(uri), true);
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
if (addHeader) {
request.setRequestHeader(addHeader.header, addHeader.value);
}
request.onreadystatechange = function () {
if (this.readyState === 4) {
request.onreadystatechange = null;
switch (this.status) {
case 200: // Operation success with content returned in response body.
case 201: // Create success.
case 204: // Operation success with no content returned in response body.
resolve(this);
break;
default: // All other statuses are unexpected so are treated like errors.
var error;
try {
error = JSON.parse(request.response).error;
} catch (e) {
error = new Error("Unexpected Error");
}
reject(error);
break;
}
}
};
request.send(JSON.stringify(data));
});
};

/**
* @function startSample
* @description Runs the sample.
* This sample demonstrates basic CRUD+ operations.
* Results are sent to the debugger's console window.
*/
Sdk.startSample = function () {
// Initializing.
Sdk.SampleVariables.deleteData = document.getElementsByName("removesampledata")[0].checked;
Sdk.SampleVariables.entitiesToDelete = []; // Reset the array.
Sdk.SampleVariables.contact1Uri = "";
Sdk.SampleVariables.account1Uri = "";
Sdk.SampleVariables.account2Uri = "";
Sdk.SampleVariables.contact2Uri = "";
Sdk.SampleVariables.opportunity1Uri = "";
Sdk.SampleVariables.competitor1Uri = "";

/**
* Behavior of this sample varies by version
* So starting by retrieving the version;
*/

Sdk.retrieveVersion()
.then(function () {
return Sdk.basicCreateAndUpdatesAsync()
})
.then(function () {
return Sdk.createWithAssociationAsync()
})
.then(function () {
return Sdk.createRelatedAsync()
})
.then(function () {
return Sdk.associateExistingAsync()
})
.then(function () {
return Sdk.deleteSampleData()
})
.catch(function (err) {
console.log("ERROR: " + err.message);
});

Sdk.retrieveVersion = function () {
return new Promise(function (resolve, reject) {
Sdk.request("GET", "/RetrieveVersion")
.then(function (request) {
try {
var RetrieveVersionResponse = JSON.parse(request.response);
var fullVersion = RetrieveVersionResponse.Version;
var versionData = fullVersion.split(".");
Sdk.versionManager.WebAPIMajorVersion = parseInt(versionData[0], 10);
Sdk.versionManager.WebAPIMinorVersion = parseInt(versionData[1], 10);
resolve();
} catch (err) {
reject(new Error("Error processing version: " + err.message))
}
})
.catch(function (err) {
reject(new Error("Error retrieving version: " + err.message))
})
});
};

Sdk.basicCreateAndUpdatesAsync = function () {
return new Promise(function (resolve, reject) {

// Section 1.
//
// Create the contact using POST request.
// A new entry will be added regardless if a contact with this info already exists in the system or not.
console.log("--Section 1 started--");
var contact = {};
contact.firstname = "Peter";
contact.lastname = "Cambel";

var entitySetName = "/contacts";

Sdk.request("POST", entitySetName, contact)


.then(function (request) {
// Process response from previous request.
Sdk.SampleVariables.contact1Uri = request.getResponseHeader("OData-EntityId");
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.contact1Uri); // To delete later
console.log("Contact 'Peter Cambel' created with URI: %s", Sdk.SampleVariables.contact1Uri);

// Setup for next request.


//
// Update contact.
// Add property values to a specific contact using PATCH request.
var contact = {};
contact.annualincome = 80000.00;
contact.jobtitle = "Junior Developer";
return Sdk.request("PATCH", Sdk.SampleVariables.contact1Uri, contact)
})
.then(function () {
// Process response from previous request.
console.log("Contact 'Peter Cambel' updated with job title and annual income.");

// Setup for next request.


//
// Retrieve selected properties of a Contact entity using GET request.
// NOTE: It is performance best practice to select only the properties you need.

// Retrieved contact properties.


var properties = [
"fullname",
"annualincome",
"jobtitle",
"description"].join();

// NOTE: For performance best practices, use $select to limit the properties you want to return
// See also: https://msdn.microsoft.com/library/gg334767.aspx#bkmk_requestProperties
var query = "?$select=" + properties;
return Sdk.request("GET", Sdk.SampleVariables.contact1Uri + query, null);
})
.then(function (request) {
// Process response from previous request.
var contact1 = JSON.parse(request.response);
var successMsg = "Contact '%s' retrieved:\n"
+ "\tAnnual income: %s \n"
+ "\tJob title: %s \n"
+ "\tDescription: %s";
console.log(successMsg,
contact1.fullname, // This property is read-only. Calculated from firstname and lastname.
contact1.annualincome,
contact1.jobtitle,
contact1.description); // Description will be "null" because it has not been set yet.

// Setup for next request.


//
//
// Update properties.
// Set new values for some of the properties and apply the values to the server via PATCH request.
// Notice that we are updating the jobtitle and annualincome properties and adding value to the
// description property in the same request.
var contact = {};
contact.jobtitle = "Senior Developer";
contact.annualincome = 95000.00;
contact.description = "Assignment to-be-determined. ";
return Sdk.request("PATCH", Sdk.SampleVariables.contact1Uri, contact);
})
.then(function () {
// Process response from previous request.
console.log("Contact 'Peter Cambel' updated:\n"
+ "\tJob title: Senior Developer, \n"
+ "\tAnnual income: 95000, \n"
+ "\tDescription: Assignment to-be-determined.");

// Setup for next request.


//
// Set value for a single property using PUT request.
// In this case, we are setting the telephone1 property to "555-0105".
var value = { value: "555-0105" };
return Sdk.request("PUT", Sdk.SampleVariables.contact1Uri + "/telephone1", value);
})
.then(function () {
// Process response from previous request.
console.log("Contact 'Peter Cambel' phone number updated.");

// Setup for next request.


//
// Retrieve single value property.
// Get a value of a single property using GET request.
// In this case, telephone1 is retrieved. We should get back "555-0105".
return Sdk.request("GET", Sdk.SampleVariables.contact1Uri + "/telephone1", null);
})
.then(function (request) {
// Process response from previous request.
var phoneNumber = JSON.parse(request.response);
console.log("Contact's phone number is: %s", phoneNumber.value);
})
.then(function () {
// Setup for next request.
//The following operations require version 8.2 or higher
if (Sdk.versionManager.WebAPIMajorVersion > 8 ||
(Sdk.versionManager.WebAPIMajorVersion == 8 && Sdk.versionManager.WebAPIMinorVersion >= 2)
) {
// Starting with December 2016 update (v8.2), a contact instance can be
// created and its properties returned in one operation by using a
//'Prefer: return=representation' header.
var contactAlt = {};
contactAlt.firstname = "Peter_Alt";
contactAlt.lastname = "Cambel";
contactAlt.jobtitle = "Junior Developer";
contactAlt.annualincome = 80000;
contactAlt.telephone1 = "555-0110";
var properties = [
"fullname",
"annualincome",
"jobtitle"].join();
var query = "?$select=" + properties;
// Create contact and return its state (in the body).
var retRepHeader = { header: "Prefer", value: "return=representation" };
Sdk.request("POST", entitySetName + query, contactAlt, retRepHeader)
.then(function (request) {
var contactA = JSON.parse(request.response);
//Because 'OData-EntityId' header not returned in a 201 response, you must instead
// construct the URI.
Sdk.SampleVariables.contactAltUri = Sdk.getClientUrl() +
Sdk.versionManager.WebAPIPath +
Sdk.versionManager.WebAPIPath +
"/contacts(" +
contactA.contactid +
")";
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.contactAltUri);
var successMsg = "Contact '%s' created:\n"
+ "\tAnnual income: %s \n"
+ "\tJob title: %s \n";
console.log(successMsg,
contactA.fullname,
contactA.annualincome,
contactA.jobtitle);
console.log("Contact URI: %s", Sdk.SampleVariables.contactAltUri);
})
.then(function () {
// Setup for next request.
//Similarly, the December 2016 update (v8.2) also enables returning selected properties
//after an update operation (PATCH), with the 'Prefer: return=representation' header.
var contactAlt = {};
contactAlt.jobtitle = "Senior Developer";
contactAlt.annualincome = 95000;
contactAlt.description = "MS Azure and Dataverse Specialist";
var properties = [
"fullname",
"annualincome",
"jobtitle",
"description"].join();
var query = "?$select=" + properties;
// Update contact and return its state (in the body).
var retRepHeader = { header: "Prefer", value: "return=representation" };
return Sdk.request("PATCH", Sdk.SampleVariables.contactAltUri + query, contactAlt, retRepHeader);
})
.then(function (request) {
// Process response from previous request.
var contactA = JSON.parse(request.response);
var successMsg = "Contact '%s' updated:\n"
+ "\tAnnual income: %s \n"
+ "\tJob title: %s \n";
console.log(successMsg,
contactA.fullname,
contactA.annualincome,
contactA.jobtitle);
//End this series of operations:
resolve();
})
.catch(function (err) {
reject(err);
});
}
else {
resolve();
}
})
.catch(function (err) {
reject(err);
});
});
};

Sdk.createWithAssociationAsync = function () {
return new Promise(function (resolve, reject) {
// Section 2.
//
// Create a new account entity and associate it with an existing contact using POST request.
console.log("\n--Section 2 started--");
var account = {};
account.name = "Contoso, Ltd.";
account.telephone1 = "555-5555";
account["[email protected]"] = Sdk.SampleVariables.contact1Uri; //relative URI ok. E.g.:
"/contacts(###)".
"/contacts(###)".

var entitySetName = "/accounts";

Sdk.request("POST", entitySetName, account)


.then(function (request) {
// Process response from previous request.
Sdk.SampleVariables.account1Uri = request.getResponseHeader("OData-EntityId");
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.account1Uri);
console.log("Account 'Contoso, Ltd.' created.");

// Setup for next request.


//
// Retrieve account's primary contact with selected properties using GET request and 'expand' query.
var contactProperties = [
"fullname",
"jobtitle",
"annualincome"
].join();
var query = "?$select=name,telephone1&$expand=primarycontactid($select=" + contactProperties + ")";
return Sdk.request("GET", Sdk.SampleVariables.account1Uri + query, null);
})
.then(function (request) {
// Process response from previous request.
var account1 = JSON.parse(request.response);
var successMsg = "Account '%s' has primary contact '%s': \n"
+ "\tJob title: %s \n"
+ "\tAnnual income: %s ";
console.log(successMsg,
account1.name,
account1.primarycontactid.fullname,
account1.primarycontactid.jobtitle,
account1.primarycontactid.annualincome);
//End this series of operations:
resolve();
})
.catch(function (err) {
reject(err);
});
});
};

Sdk.createRelatedAsync = function () {
return new Promise(function (resolve, reject) {

// Section 3.
//
// Create related entities (deep insert).
// Create the following entities in one operation using deep insert technique:
// account
// |--- contact
// |--- tasks
// Then retrieve properties of these entities
//
// Constructing the entity relationship.
console.log("\n--Section 3 started--");
var account = {};
account.name = "Fourth Coffee";
account.primarycontactid = {
firstname: "Susie",
lastname: "Curtis",
jobtitle: "Coffee Master",
annualincome: 48000.00,
Contact_Tasks: [
{
subject: "Sign invoice",
description: "Invoice #12321",
scheduledend: new Date("April 19th, 2016")
},
{
{
subject: "Setup new display",
description: "Theme is - Spring is in the air",
scheduledstart: new Date("4/20/2016")
},
{
subject: "Conduct training",
description: "Train team on making our new blended coffee",
scheduledstart: new Date("6/1/2016")
}
]
};

var entitySetName = "/accounts";


Sdk.request("POST", entitySetName, account)
.then(function (request) {
// Process response from previous request.
Sdk.SampleVariables.account2Uri = request.getResponseHeader("OData-EntityId");
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.account2Uri);
console.log("Account 'Fourth Coffee' created.");

// Setup for next request.


//
// Retrieve account entity info using GET request and 'expand' query.
var contactProperties = [
"fullname",
"jobtitle",
"annualincome"].join();

// Expand on primarycontactid to select some of contact's properties.


// NOTE: With $expand, the CRM server will return values for the selected properties.
// The CRM Web API only supports expansions one level deep.
// See also: https://msdn.microsoft.com/library/mt607871.aspx#bkmk_expandRelated
var query = "?$select=name&$expand=primarycontactid($select=" + contactProperties + ")";
return Sdk.request("GET", Sdk.SampleVariables.account2Uri + query, null);
})
.then(function (request) {
// Process response from previous request.
var account2 = JSON.parse(request.response);
var successMsg = "Account '%s' has primary contact '%s':\n"
+ "\tJob title: %s \n"
+ "\tAnnual income: %s";
console.log(successMsg,
account2.name,
account2.primarycontactid.fullname,
account2.primarycontactid.jobtitle,
account2.primarycontactid.annualincome);

// Setup for next request.


//
// Retrieve contact entity and expanding on its tasks using GET request.
Sdk.SampleVariables.contact2Uri = Sdk.getClientUrl() + Sdk.versionManager.WebAPIPath + "/contacts(" +
account2.primarycontactid.contactid + ")"; //Full URI.
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.contact2Uri); // For Susie Curtis
var contactProperties = ["fullname", "jobtitle"].join();
var contactTaskProperties = ["subject", "description", "scheduledstart", "scheduledend"].join();

// Expand on contact_tasks to select some of its properties for each task.


var query = "?$select=" + contactProperties +
"&$expand=Contact_Tasks($select=" + contactTaskProperties + ")";
return Sdk.request("GET", Sdk.SampleVariables.contact2Uri + query, null);
})
.then(function (request) {
// Process response from previous request.
var contact2 = JSON.parse(request.response);
console.log("Contact '%s' has the following assigned tasks:", contact2.fullname);

// construct the output string.


var successMsg = "Subject: %s \n"
+ "\tDescription: %s \n"
+ "\tStart: %s \n"
+ "\tEnd: %s \n";

for (var i = 0; i < contact2.Contact_Tasks.length; i++) {


console.log(successMsg,
contact2.Contact_Tasks[i].subject,
contact2.Contact_Tasks[i].description,
contact2.Contact_Tasks[i].scheduledstart,
contact2.Contact_Tasks[i].scheduledend
);
}

//End this series of operations:


resolve();
})
.catch(function (err) {
reject(err);
});
});
};

Sdk.associateExistingAsync = function () {
return new Promise(function (resolve, reject) {

// Section 4
//
// Entity associations:
// Associate to existing entities via the different relationship types:
// 1) 1:N relationship - Associate an existing contact to an existing account
// (e.g.: contact - Peter Cambel to account - Fourth Coffee).
// 2) N:N relationship - Associate an competitor to opportunity.

console.log("\n--Section 4 started--");
var contact = {};
contact["@odata.id"] = Sdk.SampleVariables.contact1Uri;

Sdk.request("POST", Sdk.SampleVariables.account2Uri + "/contact_customer_accounts/$ref", contact)


.then(function () {
// Process response from previous request.
console.log("Contact 'Peter Cambel' associated to account 'Fourth Coffee'.");

// Setup for next request.


//
// Verify that the reference was made as expected.
var contactProperties = ["fullname", "jobtitle"].join();

// This returns a collection of all associated contacts...in a "value" array.


var query = "/contact_customer_accounts?$select=" + contactProperties;
return Sdk.request("GET", Sdk.SampleVariables.account2Uri + query, null);
})
.then(function (request) {
// Process response from previous request.
var relatedContacts = JSON.parse(request.response).value; //collection is in the "value" array.
var successMsg = "\tName: %s, "
+ "Job title: %s ";

console.log("Contact list for account 'Fourth Coffee': ");

for (var i = 0; i < relatedContacts.length; i++) {


console.log(successMsg,
relatedContacts[i].fullname,
relatedContacts[i].jobtitle
);
}

// Setup for next request.


//
// Disassociate a contact from an account.
return Sdk.request("DELETE", Sdk.SampleVariables.account2Uri + "/contact_customer_accounts/$ref?$id=" +
Sdk.SampleVariables.contact1Uri, null);
})
.then(function () {
// Process response from previous request.
console.log("Contact 'Peter Cambel' disassociated from account 'Fourth Coffee'.");

// Setup for next request.


//
// N:N relationship:
// Associate a competitor to an opportunity.
var competitor = {};
competitor.name = "Adventure Works";
competitor.strengths = "Strong promoter of private tours for multi-day outdoor adventures.";

var entitySetName = "/competitors";


return Sdk.request("POST", entitySetName, competitor);
})
.then(function (request) {
// Process response from previous request.
Sdk.SampleVariables.competitor1Uri = request.getResponseHeader("OData-EntityId");
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.competitor1Uri);
console.log("Competitor 'Adventure Works' created.");

// Setup for next request.


//
// Create a new opportunity...
var opportunity = {};
opportunity.name = "River rafting adventure";
opportunity.description = "Sales team on a river-rafting offsite and team building.";
var entitySetName = "/opportunities";
return Sdk.request("POST", entitySetName, opportunity);
})
.then(function (request) {
// Process response from previous request.
Sdk.SampleVariables.opportunity1Uri = request.getResponseHeader("OData-EntityId");
Sdk.SampleVariables.entitiesToDelete.push(Sdk.SampleVariables.opportunity1Uri);
console.log("Opportunity 'River rafting adventure' created.");

// Setup for next request.


//
// Associate competitor to opportunity.
var competitor = {};
competitor["@odata.id"] = Sdk.SampleVariables.competitor1Uri;
return Sdk.request("POST", Sdk.SampleVariables.opportunity1Uri +
"/opportunitycompetitors_association/$ref", competitor);
})
.then(function () {
// Process response from previous request.
console.log("Opportunity 'River rafting adventure' associated with competitor 'Adventure Works'.");

// Setup for next request.


//
// Retrieve competitor entity and expanding on its opportunitycompetitors_association
// for all opportunities, using GET request.
var opportunityProperties = ["name", "description"].join();
var competitorProperties = ["name"].join();
var query = "?$select=" + competitorProperties +
"&$expand=opportunitycompetitors_association($select=" + opportunityProperties + ")";
return Sdk.request("GET", Sdk.SampleVariables.competitor1Uri + query, null);
})
.then(function (request) {
// Process response from previous request.
var competitor1 = JSON.parse(request.response);
console.log("Competitor '%s' has the following opportunities:", competitor1.name);
var successMsg = "\tName: %s, \n"
+ "\tDescription: %s";
for (var i = 0; i < competitor1.opportunitycompetitors_association.length; i++) {
console.log(successMsg,
console.log(successMsg,
competitor1.opportunitycompetitors_association[i].name,
competitor1.opportunitycompetitors_association[i].description
);
}

// Setup for next request.


//
// Disassociate competitor from opportunity.
return Sdk.request("DELETE", Sdk.SampleVariables.opportunity1Uri +
"/opportunitycompetitors_association/$ref?$id=" + Sdk.SampleVariables.competitor1Uri, null);
})
.then(function () {
// Process response from previous request.
console.log("Opportunity 'River rafting adventure' disassociated with competitor 'Adventure Works'");
//End this series of operations:
resolve();
})
.catch(function (err) {
reject(err);
});
});
};

Sdk.deleteSampleData = function () {
return new Promise(function (resolve, reject) {

// House cleaning - deleting sample data


// NOTE: If instances have a parent-child relationship, then deleting the parent will,
// by default, automatically cascade delete child instances. In this program,
// tasks related using the Contact_Tasks relationship have contact as their parent.
// Other relationships may behave differently.
// See also: https://msdn.microsoft.com/library/gg309412.aspx#BKMK_CascadingBehavior
console.log("\n--Section 5 started--");
if (Sdk.SampleVariables.deleteData) {
for (var i = 0; i < Sdk.SampleVariables.entitiesToDelete.length; i++) {
console.log("Deleting entity: " + Sdk.SampleVariables.entitiesToDelete[i]);
Sdk.request("DELETE", Sdk.SampleVariables.entitiesToDelete[i], null)
.catch(function (err) {
reject(new Error("ERROR: Delete failed --Reason: \n\t" + err.message))
});
}
resolve();
} else {
console.log("Sample data not deleted.");
resolve();
}

});
};

See also
Use the Dataverse Web API
Create a table row using the Web API
Retrieve a table row using the Web API
Update and delete table rows using the Web API
Web API Samples
Web API Basic Operations Sample
Web API Basic Operations Sample (C#)
Web API Samples (Client-side JavaScript)
Web API Query Data Sample (Client-side JavaScript)
Web API Conditional Operations Sample (Client-side JavaScript)
Web API Functions and Actions Sample (Client-side JavaScript)
Web API Conditional Operations Sample (Client-
side JavaScript)
7/19/2021 • 8 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to perform conditional operations using Microsoft Dataverse Web API using
client-side JavaScript.

NOTE
This sample implements the operations detailed in the Web API Conditional Operations Sample and uses the common
client-side JavaScript constructs described in Web API Samples (Client-side JavaScript)

Prerequisites
To run this sample, the following is required:
Access to Dataverse environment.
A user account with privileges to import solutions and perform CRUD operations, typically a system
administrator or system customizer security role.

Run this sample


To run this sample, download the solution package from here, extract the contents, and locate the
WebAPIConditionalOperations_1_0_0_0_managed.zip managed solution. Import the managed solution into your
Dataverse environment and view the solution configuration page to run the sample. For instructions on how to
import the sample solution, see Web API Samples (Client-side JavaScript).

Code sample
This sample includes two web resources:
WebAPIConditionalOperations.html
WebAPIConditionalOperations.js

WebAPIConditionalOperations.html
The WebAPIConditionalOperations.html web resource provides the context in which the JavaScript code will run.
<!DOCTYPE html>
<html>
<head>
<title>Microsoft CRM Web API Conditional Operations Example</title>
<meta charset="utf-8" />
<script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
<script src="scripts/es6promise.js"></script>
<script src="scripts/WebAPIConditionalOperations.js"></script>

<style type="text/css">
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
</style>
</head>
<body>
<h1>Microsoft CRM Web API Conditional Operations Example</h1>
<p>This page demonstrates the CRM Web API's Conditional Operations using JavaScript.</p>

<h2>Instructions</h2>
<p>
Choose your preferences and run the JavaScript code.
Use your browser's developer tools to view the output written to the console (e.g.: in IE11 or Microsoft
Edge,
press F12 to load the Developer Tools).
</p>
<form id="preferences">
<p>This sample deletes the single record it creates.</p>
<input type="button" name="start_samples" value="Start Sample" onclick="Sdk.startSample()" />
</form>
</body>
</html>

WebAPIConditionalOperations.js
The WebAPIConditionalOperations.js web resource is the JavaScript library that defines the operations this
sample performs.

"use strict";
var Sdk = window.Sdk || {};
/**
* @function getClientUrl
* @description Get the client URL.
* @return {string} The client URL.
*/
Sdk.getClientUrl = function () {
var context;
// GetGlobalContext defined by including reference to
// ClientGlobalContext.js.aspx in the HTML page.
if (typeof GetGlobalContext != "undefined")
{ context = GetGlobalContext(); }
else
{
if (typeof Xrm != "undefined") {
// Xrm.Page.context defined within the Xrm.Page object model for form scripts.
context = Xrm.Page.context;
}
else { throw new Error("Context is not available."); }
}
return context.getClientUrl();
}

// Global variables.
var clientUrl = Sdk.getClientUrl(); // e.g.: https://org.crm.dynamics.com
var webAPIPath = "/api/data/v8.1"; // Path to the web API.
var webAPIPath = "/api/data/v8.1"; // Path to the web API.
var account1Uri; // e.g.: Contoso Ltd (sample)
var initialAcctETagVal; // The initial ETag value of the account created
var updatedAcctETagVal; // The ETag value of the account after it is updated

// Entity properties to select in a request.


var contactProperties = ["fullname", "jobtitle", "annualincome"];
var accountProperties = ["name"];
var taskProperties = ["subject", "description"];

/**
* @function request
* @description Generic helper function to handle basic XMLHttpRequest calls.
* @param {string} action - The request action. String is case-sensitive.
* @param {string} uri - An absolute or relative URI. Relative URI starts with a "/".
* @param {object} data - An object representing an entity. Required for create and update actions.
* @param {object} addHeader - An object with header and value properties to add to the request
* @returns {Promise} - A Promise that returns either the request object or an error object.
*/
Sdk.request = function (action, uri, data, addHeader) {
if (!RegExp(action, "g").test("POST PATCH PUT GET DELETE")) { // Expected action verbs.
throw new Error("Sdk.request: action parameter must be one of the following: " +
"POST, PATCH, PUT, GET, or DELETE.");
}
if (!typeof uri === "string") {
throw new Error("Sdk.request: uri parameter must be a string.");
}
if ((RegExp(action, "g").test("POST PATCH PUT")) && (!data)) {
throw new Error("Sdk.request: data parameter must not be null for operations that create or modify
data.");
}
if (addHeader) {
if (typeof addHeader.header != "string" || typeof addHeader.value != "string") {
throw new Error("Sdk.request: addHeader parameter must have header and value properties that are
strings.");
}
}

// Construct a fully qualified URI if a relative URI is passed in.


if (uri.charAt(0) === "/") {
uri = clientUrl + webAPIPath + uri;
}

return new Promise(function (resolve, reject) {


var request = new XMLHttpRequest();
request.open(action, encodeURI(uri), true);
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
if (addHeader) {
request.setRequestHeader(addHeader.header, addHeader.value);
}
request.onreadystatechange = function () {
if (this.readyState === 4) {
request.onreadystatechange = null;
switch (this.status) {
case 200: // Success with content returned in response body.
case 204: // Success with no content returned in response body.
case 304: // Success with Not Modified.
resolve(this);
break;
default: // All other statuses are error cases.
var error;
try {
error = JSON.parse(request.response).error;
} catch (e) {
error = new Error("Unexpected Error");
}
reject(error);
reject(error);
break;
}
}
};
request.send(JSON.stringify(data));
});
};

/**
* @function startSample
* @description Runs the sample.
* This sample demonstrates conditional operations using CRM Web API.
* Results are sent to the debugger's console window.
*/
Sdk.startSample = function () {
// Initializing...
console.log("-- Sample started --");

// Create the CRM account instance.


var account = {
name: "Contoso, Ltd",
telephone1: "555-0000",// Phone number value will increment with each update attempt.
revenue: 5000000,
description: "Parent company of Contoso Pharmaceuticals, etc."
};

var uri = "/accounts"; // A relative URi to the account entity.


Sdk.request("POST", uri, account)
.then( function (request) {
console.log("Account entity created.");
// Assign the Uri to the created account to a global variable.
account1Uri = request.getResponseHeader("OData-EntityId");

// Retrieve the created account entity.


return Sdk.request("GET", account1Uri + "?$select=name,revenue,telephone1,description");
})
.then( function (request) {
// Show the current entity properties.
var account = JSON.parse(request.response);
console.log(JSON.stringify(account, null, 2));

initialAcctETagVal = account["@odata.etag"]; // Save the current ETag value.

// Conditional Get START.


// Attempt to retrieve using conditional GET with current ETag value.
// Expecting nothing in the response because entity was not modified.
console.log("-- Conditional GET section started --");
var ifNoneMatchETag = { header: "If-None-Match", value: initialAcctETagVal };
return Sdk.request("GET", account1Uri + "?$select=name,revenue,telephone1,description", null,
ifNoneMatchETag);
})
.then( function (request) {
console.log("Instance retrieved using ETag: %s", initialAcctETagVal);
if (request.status == 304) {
//Expected:
console.log("\tEntity was not modified so nothing was returned.")
console.log(request.response); //Nothing
}
else {
//Not Expected:
console.log(JSON.stringify(JSON.parse(request.response), null, 2));
}

// Modify the account instance by updating telephone1.


// This request operation will also update the ETag value.
return Sdk.request("PUT", account1Uri + "/telephone1", { value: "555-0001" })
} )
.then( function (request) {
console.log("Account telephone number updated.");

// Re-attempt conditional GET with original ETag value.


var ifNoneMatchETag = { header: "If-None-Match", value: initialAcctETagVal };
return Sdk.request("GET", account1Uri + "?$select=name,revenue,telephone1,description", null,
ifNoneMatchETag);
} )
.then( function (request) {
if (request.status == 200) {
// Expected.
console.log("Instance retrieved using ETag: %s", initialAcctETagVal);
var account = JSON.parse(request.response);
updatedAcctETagVal = account["@odata.etag"]; //Capture updated ETag.
console.log(JSON.stringify(account, null, 2));
}
else {
// Not Expected.
console.log("Unexpected status: %s", request.status)
}
// Conditional Get END.

// Optimistic concurrency on delete and update START.


console.log("-- Optimistic concurrency section started --");
// Attempt to delete original account (only if matches original ETag value).
var ifMatchETag = { header: "If-Match", value: initialAcctETagVal };
return Sdk.request("DELETE", account1Uri, null, ifMatchETag);
} )
.then( function (request) {
// Success not expected.
console.log("Unexpected status: %s", request.status)
},
// Catch error.
function (error) {
// DELETE: Precondition failed error expected.
console.log("Expected Error: %s", error.message);
console.log("\tAccount not deleted using ETag '%s', status code: '%s'.", initialAcctETagVal, 412)

// Attempt to update account (if matches original ETag value).


var accountUpdate = {
telephone1: "555-0002",
revenue: 6000000
};
var ifMatchETag = { header: "If-Match", value: initialAcctETagVal };
return Sdk.request("PATCH", account1Uri, accountUpdate, ifMatchETag);
})
.then( function (request) {
// Success not expected.
console.log("Unexpected status: %s", request.status);
},
// Catch error.
function (error) {
// UPDATE: Precondition failed error expected.
console.log("Expected Error: %s", error.message);
console.log("\tAccount not updated using ETag '%s', status code: '%s'.", initialAcctETagVal, 412)

// Re-attempt update if matches current ETag value.


var accountUpdate = {
telephone1: "555-0003",
revenue: 6000000
};
var ifMatchETag = { header: "If-Match", value: updatedAcctETagVal };
return Sdk.request("PATCH", account1Uri, accountUpdate, ifMatchETag);
} )
.then( function (request) {
if (request.status == 204) //No Content
{
// Expected.
console.log("Account successfully updated using ETag '%s', status code: '%s'.",
updatedAcctETagVal,
request.status)
}
else {
// Not Expected.
console.log("Unexpected status: %s", request.status)
}
// Retrieve and output current account state.
return Sdk.request("GET", account1Uri + "?$select=name,revenue,telephone1,description");
} )
.then( function (request) {
var account = JSON.parse(request.response);
updatedAcctETagVal = account["@odata.etag"]; // Capture updated ETag.
console.log(JSON.stringify(account, null, 2));
// Optimistic concurrency on delete and update END.

// Controlling upsert operations START.


console.log("-- Controlling upsert operations section started --");

// Attempt to insert (without update) some properties for this account.


var accountUpsert = {
telephone1: "555-0004",
revenue: 7500000
};
var ifNoneMatchResource = { header: "If-None-Match", value: "*" };
return Sdk.request("PATCH", account1Uri, accountUpsert, ifNoneMatchResource);
} )
.then( function (request) {
// Success not expected.
console.log("Unexpected status: %s", request.status);
},
// Catch error.
function (error) {
// Precondition failed error expected.
console.log("Expected Error: %s", error.message);
console.log("\tAccount not updated using ETag '%s', status code: '%s'.", initialAcctETagVal, 412)

// Attempt to perform same update without creation.


var accountUpsert = {
telephone1: "555-0005",
revenue: 7500000
};
// Perform operation only if matching resource exists.
var ifMatchResource = { header: "If-Match", value: "*" };
return Sdk.request("PATCH", account1Uri, accountUpsert, ifMatchResource);
} )
.then( function (request) {
if (request.status == 204) // No Content.
{
// Expected.
console.log("Account updated using If-Match '*'")
}
else {
// Not Expected.
console.log("Unexpected status: %s", request.status)
}

// Retrieve and output current account state.


return Sdk.request("GET", account1Uri + "?$select=name,revenue,telephone1,description");
})
.then( function (request) {
var account = JSON.parse(request.response);
updatedAcctETagVal = account["@odata.etag"]; // Capture updated ETag.
console.log(JSON.stringify(account, null, 2));

// Controlling upsert operations END.

// Prevent update of deleted entity START.


// Delete the account.
return Sdk.request("DELETE", account1Uri);
return Sdk.request("DELETE", account1Uri);
}
)
.then(
function (request) {
if (request.status == 204) {
console.log("Account was deleted");

// Attempt to update it.


var accountUpsert = {
telephone1: "555-0005",
revenue: 7500000
};
// Perform operation only if matching resource exists.
var ifMatchResource = { header: "If-Match", value: "*" };
return Sdk.request("PATCH", account1Uri, accountUpsert, ifMatchResource);
}
} )
.then( function (request) {
// Success not expected.
// Without the If-Match header while using PATCH a new entity would have been created with the
// same ID as the deleted entity.
console.log("Unexpected status: %s", request.status);

},
// Catch error.
function (error) {
// Not found error expected.
console.log("Expected Error: %s", error.message);
console.log("\tAccount not updated because it doesn't exist.");
}

)
.catch(function (error) {
console.log(error.message);
});
}

See also
Use the Dataverse Web API
Perform conditional operations using the Web API
Web API Samples
Web API Conditional Operations Sample
Web API Conditional Operations Sample (C#)
Web API Samples (Client-side JavaScript)
Web API Basic Operations Sample (Client-side JavaScript)
Web API Query Data Sample (Client-side JavaScript)
Web API Functions and Actions Sample (Client-side JavaScript)
Web API Functions and Actions Sample (Client-side
JavaScript)
7/19/2021 • 15 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to perform bound and unbound functions and actions, including custom actions,
using the Microsoft Dataverse Web API using client-side JavaScript.

NOTE
This sample implements the operations detailed in the Web API Functions and Actions Sample and uses the common
client-side JavaScript constructs described in Web API Samples (Client-side JavaScript)

In this section
Prerequisites
Run this sample
Code sample

Prerequisites
To run this sample, the following is required:
Access to Dataverse environment.
A user account with privileges to import solutions and perform CRUD operations, typically a system
administrator or system customizer security role.

Run this sample


To run this sample, download the solution package from here, extract the contents, and locate the
WebAPIFunctionsandActions_1_0_0_0_managed.zip managed solution file. Import the managed solution into your
Dataverse organization and view the configuration page of the solution to run the sample. For instructions on
how to import the sample solution, see Web API Samples (Client-side JavaScript).

Code sample
This sample includes two web resources:
WebAPIFunctionsAndActions.html
WebAPIFunctionsAndActions.js

WebAPIFunctionsAndActions.html
The WebAPIFunctionsAndActions.html web resource provides the context in which the JavaScript code will run.
<!DOCTYPE html>
<html>
<head>
<title>Microsoft CRM Web API Functions and Actions Example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
<script src="scripts/es6promise.js"></script>
<script src="scripts/WebAPIFunctionsAndActions.js"></script>

<style type="text/css">
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#preferences {
border: inset;
padding: 10px 10px;
}

#output_area {
border: inset;
background-color: gainsboro;
padding: 10px 10px;
}
</style>
</head>
<body>
<h1>Microsoft CRM Web API Functions and Actions Example</h1>
<p>This page demonstrates the CRM Web API's Functions and Actions using JavaScript.</p>

<h2>Instructions</h2>
<p>
Choose your preferences and run the JavaScript code.
Use your browser's developer tools to view the output written to the console (e.g.: in IE11 or Microsoft
Edge,
press F12 to load the Developer Tools).
</p>
<form id="preferences">
<p>
Remove sample data (Choose whether you want to delete sample data created for this sample):<br />
<input name="removesampledata" type="radio" value="yes" checked /> Yes
<input name="removesampledata" type="radio" value="no" /> No
</p>
<input type="button" name="start_samples" value="Start Samples" onclick="Sdk.startSample()" />
</form>

</body>
</html>

WebAPIFunctionsAndActions.js
The WebAPIFunctionsAndActions.js web resource is the JavaScript library that defines the operations this
sample performs.

"use strict";
var Sdk = window.Sdk || {};

/**
* @function getClientUrl
* @description Get the client URL.
* @return {string} The client URL.
*/
Sdk.getClientUrl = function () {
Sdk.getClientUrl = function () {
var context;
// GetGlobalContext defined by including reference to
// ClientGlobalContext.js.aspx in the HTML page.
if (typeof GetGlobalContext != "undefined") {
context = GetGlobalContext();
} else {
if (typeof Xrm != "undefined") {
// Xrm.Page.context defined within the Xrm.Page object model for form scripts.
context = Xrm.Page.context;
} else {
throw new Error("Context is not available.");
}
}
return context.getClientUrl();
};

// Global variables
var entitiesToDelete = []; // Entity URIs to be deleted later
// (if user chooses to delete sample data).
var deleteData = true; // Controls whether sample data are deleted at the end of this
sample run.
var clientUrl = Sdk.getClientUrl(); // ie.: https://org.crm.dynamics.com
var webAPIPath = "/api/data/v8.1"; // Path to the web API.
var incidentUri; // Incident created with three closed tasks.
var opportunityUri; // Closed opportunity to re-open before deleting.
var letterUri; // Letter to add to contact's queue.
var myQueueUri; // The contact's queue uri.
var contactUri; // Add a note to this contact.
var CUSTOMERACCOUNTNAME = "Account Customer Created in WebAPIFunctionsAndActions sample"; // For custom
action.

/**
* @function getWebAPIPath
* @description Get the full path to the Web API.
* @return {string} The full URL of the Web API.
*/
Sdk.getWebAPIPath = function () {
return Sdk.getClientUrl() + webAPIPath;
}

/**
* @function request
* @description Generic helper function to handle basic XMLHttpRequest calls.
* @param {string} action - The request action. String is case-sensitive.
* @param {string} uri - An absolute or relative URI. Relative URI starts with a "/".
* @param {object} data - An object representing an entity. Required for create and update actions.
* @param {object} addHeader - An object with header and value properties to add to the request
* @returns {Promise} - A Promise that returns either the request object or an error object.
*/
Sdk.request = function (action, uri, data, addHeader) {
if (!RegExp(action, "g").test("POST PATCH PUT GET DELETE")) { // Expected action verbs.
throw new Error("Sdk.request: action parameter must be one of the following: " +
"POST, PATCH, PUT, GET, or DELETE.");
}
if (!typeof uri === "string") {
throw new Error("Sdk.request: uri parameter must be a string.");
}
if ((RegExp(action, "g").test("POST PATCH PUT")) && (!data)) {
throw new Error("Sdk.request: data parameter must not be null for operations that create or modify
data.");
}
if (addHeader) {
if (typeof addHeader.header != "string" || typeof addHeader.value != "string") {
throw new Error("Sdk.request: addHeader parameter must have header and value properties that are
strings.");
}
}

// Construct a fully qualified URI if a relative URI is passed in.


// Construct a fully qualified URI if a relative URI is passed in.
if (uri.charAt(0) === "/") {
uri = clientUrl + webAPIPath + uri;
}

return new Promise(function (resolve, reject) {


var request = new XMLHttpRequest();
request.open(action, encodeURI(uri), true);
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
if (addHeader) {
request.setRequestHeader(addHeader.header, addHeader.value);
}
request.onreadystatechange = function () {
if (this.readyState === 4) {
request.onreadystatechange = null;
switch (this.status) {
case 200: // Success with content returned in response body.
case 204: // Success with no content returned in response body.
case 304: // Success with Not Modified
resolve(this);
break;
default: // All other statuses are error cases.
var error;
try {
error = JSON.parse(request.response).error;
} catch (e) {
error = new Error("Unexpected Error");
}
reject(error);
break;
}
}
};
request.send(JSON.stringify(data));
});
};

/**
* @function Sdk.startSample
* @description Initiates a chain of promises to show use of Functions and Actions with the Web API.
* Functions and actions represent re-usable operations you can perform using the Web API.
* For more info, see https://msdn.microsoft.com/library/mt607990.aspx#bkmk_actions
* The following standard CRM Web API functions and actions are invoked:
* - WhoAmI, a basic unbound function
* - GetTimeZoneCodeByLocalizedName, an unbound function that requires parameters
* - CalculateTotalTimeIncident, a bound function
* - WinOpportunity, an unbound action that takes parameters
* - AddToQueue, a bound action that takes parameters
* - In addition, a custom bound and an unbound action contained within the solution are invoked.
*/
Sdk.startSample = function () {
// Initializing.
deleteData = document.getElementsByName("removesampledata")[0].checked;
entitiesToDelete = []; // Reset the array.

console.log("-- Sample started --");

// Create the CRM entry intances used by this sample program.


Sdk.createRequiredRecords()
.then(function () {
console.log("-- Working with functions --");
// Bound and Unbound functions
// See https://msdn.microsoft.com/library/gg309638.aspx#bkmk_boundAndUnboundFunctions

console.log("Using functions to look up your full name.");


// Calling a basic unbound function without parameters.
// Retrieves the user's full name using a series of function requests.
// Retrieves the user's full name using a series of function requests.
// - Call WhoAmI via the Sdk.getUsersFullName function.
// For more info on the WhoAmI function, see https://msdn.microsoft.com/library/mt607925.aspx
return Sdk.getUsersFullName();
})
.then(function (fullName) {
console.log("\tYour full name is: %s\n", fullName);

console.log("Unbound function: GetTimeZoneCodeByLocalizedName");


// Calling a basic unbound function with no parameters.
// Retrieves the time zone code for the specified time zone.
// - Pass parameters to an unbound function by calling the GetTimeZoneCodeByLocalizedName Function.
// For more info, see https://msdn.microsoft.com/library/mt607644.aspx
var localizedStandardName = 'Pacific Standard Time';
var localeId = 1033;
// Demonstrates best practice of passing parameters.
var uri = ["/GetTimeZoneCodeByLocalizedName",
"(LocalizedStandardName=@p1,LocaleId=@p2)",
"?@p1='" + localizedStandardName + "'&@p2=" + localeId];

/* This would also work:


var uri = ["/GetTimeZoneCodeByLocalizedName",
"(LocalizedStandardName='" + localizedStandardName + "',LocaleId=" + localeId + ")"];
*/

return Sdk.request("GET", uri.join("")) // Send request.


})
.then(function (request) {
// Returns GetTimeZoneCodeByLocalizedNameResponse ComplexType.
// For more info, see https://msdn.microsoft.com/library/mt607889.aspx
var localizedStandardName = 'Pacific Standard Time';
var timeZoneCode = JSON.parse(request.response).TimeZoneCode;
console.log("\tFunction returned time zone %s, with code '%s'.", localizedStandardName, timeZoneCode);

console.log("Bound function: CalculateTotalTimeIncident");


// Calling a basic bound function that requires parameters.
// Retrieve the total time, in minutes, spent on all tasks associated with this incident.
// - Use CalculateTotalTimeIncident to get the total duration of all closed activities.
// For more info, see https://msdn.microsoft.com/library/mt593054.aspx
// Note that in a bound function the full function name includes the
// namespace Microsoft.Dynamics.CRM. Functions that aren’t bound must not use the full name.
return Sdk.request("GET", incidentUri + "/Microsoft.Dynamics.CRM.CalculateTotalTimeIncident()")
})
.then(function (request) {
// Returns CalculateTotalTimeIncidentResponse ComplexType.
// For more info, see https://msdn.microsoft.com/library/mt607924.aspx
var totalTime = JSON.parse(request.response).TotalTime; //returns 90
console.log("\tFunction returned %s minutes - total duration of tasks associated with the incident.\n",
totalTime);

console.log("-- Working with Actions --");


// For more info about Action, see https://msdn.microsoft.com/library/mt607600.aspx

console.log("Unbound Action: WinOpportunity");


// Calling an unbound action that requires parameters.
// Closes an opportunity and markt it as won.
// - Update the WinOpportunity (created by Sdk.createRequiredRecords()) by closing it as won.
// Use WinOpportunity Action (https://msdn.microsoft.com/library/mt607971.aspx)
// This action does not return a value
var parameters = {
"Status": 3,
"OpportunityClose": {
"subject": "Won Opportunity",
"[email protected]": opportunityUri
}
}

return Sdk.request("POST", "/WinOpportunity", parameters)


})
.then(function () {
console.log("\tOpportunity won.");

console.log("Bound Action: AddToQueue");


// Calling a bound action that requires parameters.
// Adds a new letter tracking activity to the current user's queue.
// The letter was created as part of the Sdk.createRequiredRecords().
// - Get a reference to the current user.
// - Get a reference to the letter activity.
// - Add letter to current user's queue via the bound action AddToQueue.
// For more info on AddToQueue, see https://msdn.microsoft.com/library/mt607880.aspx

return Sdk.request("GET", "/WhoAmI");


})
.then(function (request) {
var whoAmIResponse = JSON.parse(request.response);
var myId = whoAmIResponse.UserId;

// Get a reference to the current user.


return Sdk.request("GET", Sdk.getWebAPIPath() + "/systemusers(" + myId + ")/queueid/$ref")
})
.then(function (request) {
myQueueUri = JSON.parse(request.response)["@odata.id"];

// Get a reference to the letter activity.


return Sdk.request("GET", letterUri + "?$select=activityid")
})
.then(function (request) {

var letterActivityId = JSON.parse(request.response).activityid

var parameters = {
Target: {
activityid: letterActivityId,
"@odata.type": "Microsoft.Dynamics.CRM.letter"
}
}
//Adding the letter to the user's default queue.
return Sdk.request("POST", myQueueUri + "/Microsoft.Dynamics.CRM.AddToQueue", parameters);
})
.then(function (request) {
var queueItemId = JSON.parse(request.response).QueueItemId;
console.log("\tQueueItemId returned from AddToQueue Action: %s\n", queueItemId);

console.log("-- Working with custom actions --");


console.log("Custom action: sample_AddNoteToContact");
// Add a note to an existing contact.
// This operation calls a custom action named sample_AddNoteToContact.
// This custom action is installed when you install this sample's solution to your CRM server.
// - Add a note to an existing contact (e.g.: contactUri)
// - Get the note info and the contact's full name.
// For more info, see https://msdn.microsoft.com/library/mt607600.aspx#bkmk_customActions
//sample_AddNoteToContact custom action parameters
var parameters = {
NoteTitle: "The Title of the Note",
NoteText: "The text content of the note."
}
return Sdk.request("POST", contactUri + "/Microsoft.Dynamics.CRM.sample_AddNoteToContact", parameters)
})
.then(function (request) {
var annotationid = JSON.parse(request.response).annotationid;
var annotationUri = Sdk.getWebAPIPath() + "/annotations(" + annotationid + ")";
// The annotation will be deleted with the contact when it is deleted.

return Sdk.request("GET", annotationUri + "?


$select=subject,notetext&$expand=objectid_contact($select=fullname)")
})
.then(function (request) {
var annotation = JSON.parse(request.response);
console.log("\tA note with the title '%s' and the content '%s' was created and associated with the contact
%s.\n",
annotation.subject, annotation.notetext, annotation.objectid_contact.fullname);

console.log("Custom action: sample_CreateCustomer");


// Create a customer of a specified type using the custom action sample_CreateCustomer.
// - Shows how create a valid customer of type "account".
// - Shows how to handle exception from a custom action.

var parameters = {
CustomerType: "account",
AccountName: CUSTOMERACCOUNTNAME
}

// Create the account. This is a valid request


return Sdk.request("POST", "/sample_CreateCustomer", parameters)
})
.then(function (request) {
// Retrieve the account we just created
return Sdk.request("GET", "/accounts?$select=name&$filter=name eq '" + CUSTOMERACCOUNTNAME + "'");
})
.then(function (request) {
var customerAccount = JSON.parse(request.response).value[0];
var customerAccountId = customerAccount.accountid;
var customerAccountIdUri = Sdk.getWebAPIPath() + "/accounts(" + customerAccountId + ")";
entitiesToDelete.push(customerAccountIdUri);
console.log("\tAccount customer created with the name '%s'", customerAccount.name);

// Create a contact but uses invalid parameters


// - Throws an error intentionally
return new Promise(function (resolve, reject) {
var parameters = {
CustomerType: "contact",
AccountName: CUSTOMERACCOUNTNAME //not valid for contact
// e.g.: ContactFirstName and ContactLastName are required when CustomerType is "contact".
}
Sdk.request("POST", "/sample_CreateCustomer", parameters) // This request is expected to fail.
.then(function () {
console.log("Not expected.")
reject(new Error("Call to sample_CreateCustomer not expected to succeed."))
})
.catch(function (err) {
//Expected error
console.log("\tExpected custom error: " + err.message); // Custom action can return custom error
messages.
resolve(); // Show the error but resolve the thread so sample can continue.
});
});
})
.then(function () {
// House cleaning.
console.log("\n-- Deleting sample data --");
if (deleteData) {
return Sdk.deleteEntities();
}
else {
console.log("Sample data not deleted.");
}
})
.catch(function (err) {
console.log("ERROR: " + err.message);
});
}

/**
* @function Sdk.deleteEntities
* @description Deletes the entities created by this sample
*/
Sdk.deleteEntities = function () {
Sdk.deleteEntities = function () {
return new Promise(function (resolve, reject) {

entitiesToDelete.unshift(opportunityUri) // Adding to the beginning so it will get deleted before the


parent account.
// Re-open the created opportunity so it can be deleted.
Sdk.request("PATCH", opportunityUri, { statecode: 0, statuscode: 2 })
.then(function () {
// Get the opportunityclose URI so it can be deleted
return Sdk.request("GET", opportunityUri + "/Opportunity_OpportunityClose/$ref")
})
.then(function (request) {
var opportunityCloseUri = JSON.parse(request.response).value[0]["@odata.id"];

// Adding to the opportunityclose URI it will get deleted before the opportunity.
entitiesToDelete.unshift(opportunityCloseUri)

/*
These deletions have to be done consecutively in a specific order to avoid a Generic SQL error
which can occur because of relationship behavior actions for the delete event.
*/

return Sdk.request("DELETE", entitiesToDelete[0]) //opportunityclose


})
.then(function () {
console.log(entitiesToDelete[0] + " Deleted");
return Sdk.request("DELETE", entitiesToDelete[1]) //opportunity
})
.then(function () {
console.log(entitiesToDelete[1] + " Deleted");
return Sdk.request("DELETE", entitiesToDelete[2])//account
})
.then(function () {
console.log(entitiesToDelete[2] + " Deleted");
return Sdk.request("DELETE", entitiesToDelete[3]) //Fourth Coffee account
})
.then(function () {
console.log(entitiesToDelete[3] + " Deleted");
return Sdk.request("DELETE", entitiesToDelete[4]) //Letter
})
.then(function () {
console.log(entitiesToDelete[4] + " Deleted");
return Sdk.request("DELETE", entitiesToDelete[5]) //Contact
})
.then(function () {
console.log(entitiesToDelete[5] + " Deleted");
return Sdk.request("DELETE", entitiesToDelete[6]) //AccountCustomer
})
.then(function () {
console.log(entitiesToDelete[6] + " Deleted");
resolve();
})
.catch(function (err) {
reject(new Error("Error from Sdk.deleteEntities: " + err.message));
});
});
};

/**
* @function Sdk.getUsersFullName
* @description Retrieves the current user's full name.
* @returns {Promise} - A Promise that returns the full name of the user
*/
Sdk.getUsersFullName = function () {
return new Promise(function (resolve, reject) {
//Use WhoAmI Function (https://msdn.microsoft.com/library/mt607925.aspx)
Sdk.request("GET", "/WhoAmI")
.then(function (request) {
//Returns WhoAmIResponse ComplexType (https://msdn.microsoft.com/library/mt607982.aspx)
var myId = JSON.parse(request.response).UserId;
var myId = JSON.parse(request.response).UserId;
//Retrieve the systemuser Entity fullname property (https://msdn.microsoft.com/library/mt608065.aspx)
return Sdk.request("GET", "/systemusers(" + myId + ")?$select=fullname")
})
.then(function (request) {
//Return the users full name
resolve(JSON.parse(request.response).fullname);
})
.catch(function (err) {
reject("Error in Sdk.getUsersFullName function: " + err.message);
});
});
};

/**
* @function Sdk.createRequiredRecords
* @description Creates data required by this sample program.
* - Create an account with three 30 minute tasks.
* - Create another account associated with an opportunity.
* - Create a letter.
* - Create a contact.
* @returns {Promise} - resolve the promise if all goes well; reject otherwise.
*/
Sdk.createRequiredRecords = function () {
console.log("-- Creating sample data --");
// Create a parent account, an associated incident with three
// associated tasks(required for CalculateTotalTimeIncident).
return new Promise(function (resolve, reject) {
Sdk.createAccountWithIncidentAndThree30MinuteClosedTasks()
.then(function (iUri) {
incidentUri = iUri;

//Create another account and associated opportunity (required for CloseOpportunityAsWon).


return Sdk.createAccountWithOpportunityToWin();
})
.then(function (oUri) {
opportunityUri = oUri;

// Create a letter to use with AddToQueue action.


var letter = {
description: "Example letter"
}
return Sdk.request("POST", "/letters", letter)
})
.then(function (request) {
letterUri = request.getResponseHeader("OData-EntityId");
entitiesToDelete.push(letterUri);

// Create a contact to use with custom action sample_AddNoteToContact


var contact = {
firstname: "Jon",
lastname: "Fogg"
}
return Sdk.request("POST", "/contacts", contact)
})
.then(function (request) {
contactUri = request.getResponseHeader("OData-EntityId");
entitiesToDelete.push(contactUri);

resolve()
})
.catch(function (err) {
reject("Error in Sdk.createRequiredRecords function: " + err.message);
});
});
}

/**
* @function Sdk.createAccountwithIncidentAndThree30MinuteClosedTasks
* @description Create an account and associate three 30 minute tasks. Close the tasks.
* @description Create an account and associate three 30 minute tasks. Close the tasks.
* @returns {Promise} - A Promise that returns the uri of an incident created.
*/
Sdk.createAccountWithIncidentAndThree30MinuteClosedTasks = function () {
return new Promise(function (resolve, reject) {
var iUri; // incidentUri
// Create a parent account for the incident.
Sdk.request("POST", "/accounts", { name: "Fourth Coffee" })
.then(function (request) {
// Capture the URI of the created account so it can be deleted later.
var accountUri = request.getResponseHeader("OData-EntityId");
entitiesToDelete.push(accountUri);
// Define an incident associated with the account with three related tasks.
// Each task has a 30 minute duration.
var incident = {
title: "Sample Case",
"[email protected]": accountUri,
Incident_Tasks: [
{
subject: "Task 1",
actualdurationminutes: 30
},
{
subject: "Task 2",
actualdurationminutes: 30
},
{
subject: "Task 3",
actualdurationminutes: 30
}
]
};
// Create the incident and related tasks.
return Sdk.request("POST", "/incidents", incident)
})
.then(function (request) {
iUri = request.getResponseHeader("OData-EntityId");

// Retrieve references to the tasks created.


return Sdk.request("GET", iUri + "/Incident_Tasks/$ref")
})
.then(function (request) {
// Capture the URL for the three tasks in this array.
var taskReferences = [];
JSON.parse(request.response).value.forEach(function (tr) {
taskReferences.push(tr["@odata.id"]);
});
// An array to hold a set of promises.
var promises = [];
// The data to use to update the tasks so that they are closed.
var update = {
statecode: 1, //Completed
statuscode: 5 //Completed
}
// Fill the array with promises
taskReferences.forEach(function (tr) {
promises.push(Sdk.request("PATCH", tr, update))
})
// When all the promises resolve, return a promise.
return Promise.all(promises);
})
.then(function () {
// Return the incident URI to the calling code.
resolve(iUri);
})
.catch(function (err) {
// Differentiate the message for any error returned by this function.
reject(new Error("ERROR in Sdk.createAccountwithIncidentAndThree30MinuteClosedTasks function: " +
err.message))
});
});
});
}

/**
* @function Sdk.createAccountwithOpportunityToWin
* @description Create an account and an associated opportunity.
* @returns {Promise} - A Promise that returns the uri of an opportunity.
*/
Sdk.createAccountWithOpportunityToWin = function () {
return new Promise(function (resolve, reject) {
var accountUri;
var account = {
name: "Sample Account for WebAPIFunctionsAndActions sample",
opportunity_customer_accounts: [{
name: "Opportunity to win"
}]
};
Sdk.request("POST", "/accounts", account) // Create the account.
.then(function (request) {
accountUri = request.getResponseHeader("OData-EntityId");
entitiesToDelete.push(accountUri);

// Retrieve the opportunity's reference.


return Sdk.request("GET", accountUri + "/opportunity_customer_accounts/$ref")
})
.then(function (request) {
var oUri = JSON.parse(request.response).value[0]["@odata.id"];
resolve(oUri); // Return the opportunity's uri.
})
.catch(function (err) {
reject(new Error("Error in Sdk.createAccountwithOpportunityToWin: " + err.message));
});
});
};

See also
Use the Dataverse Web API
Use Web API functions
Use Web API actions
Web API Samples
Web API Functions and Actions Sample
Web API Functions and Actions Sample (C#)
Web API Samples (Client-side JavaScript)
Web API Basic Operations Sample (Client-side JavaScript)
Web API Query Data Sample (Client-side JavaScript)
Web API Conditional Operations Sample (Client-side JavaScript)
Web API Query Data Sample (Client-side
JavaScript)
7/19/2021 • 19 minutes to read • Edit Online

NOTE
Unsure about entity vs. table? See Developers: Understand terminology in Microsoft Dataverse.

This sample demonstrates how to perform basic query requests using the Microsoft Dataverse Web API using
client-side JavaScript.

NOTE
This sample implements the operations detailed in the Web API Query Data Sample and uses the common client-side
JavaScript constructs described in Web API Samples (Client-side JavaScript)

Prerequisites
To run this sample, the following is required:
Access to Dataverse environment.
A user account with privileges to import solutions and perform CRUD operations, typically a system
administrator or system customizer security role.

Run this sample


To run this sample, download the solution package from here. Extract the contents of the sample and locate the
WebAPIQueryData_1_0_0_0_managed.zip managed solution file. Import the managed solution into your Dataverse
organization and run the sample. For instructions on how to import the sample solution, see Web API Samples
(Client-side JavaScript).

Code sample
This sample includes two web resources:
WebAPIQuery.html
WebAPIQuery.js

WebAPIQuery.html
The WebAPIQuery.html web resource provides the context in which the JavaScript code will run.
<!DOCTYPE html>
<html>
<head>
<title>Microsoft CRM Web API Query Example</title>
<meta charset="utf-8" />
<script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
<script src="scripts/es6promise.js"></script>
<script src="scripts/WebAPIQuery.js"></script>

<style type="text/css">
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#preferences {
border: inset;
padding: 10px 10px;
}

#output_area {
border: inset;
background-color: gainsboro;
padding: 10px 10px;
}
</style>
</head>
<body>
<h1>Microsoft CRM Web API Query Example</h1>
<p>This page demonstrates the CRM Web API's Query operations using JavaScript.</p>

<h2>Instructions</h2>
<p>Choose your preferences and run the JavaScript code.
Use your browser's developer tools to view the output written to the console (e.g.: in IE 11 or
Microsoft Edge,
press F12 to load the Developer Tools).</p>
<form id="preferences">
<p>
Remove sample data (Choose whether you want to delete sample data created during this
execution):
<br />
<input name="removesampledata" type="radio" value="yes" checked /> Yes
<input name="removesampledata" type="radio" value="no" /> No
</p>
<input type="button" name="start_samples" value="Start Sample" onclick="Sdk.startSample()" />
</form>

</body>
</html>

WebAPIQuery.js
The WebAPIQuery.js web resource is the JavaScript library that defines the operations this sample performs.

"use strict";
var Sdk = window.Sdk || {};
/**
* @function getClientUrl
* @description Get the client URL.
* @returns {string} The client URL.
*/
Sdk.getClientUrl = function () {
var context;
// GetGlobalContext defined by including reference to
// ClientGlobalContext.js.aspx in the HTML page.
if (typeof GetGlobalContext != "undefined")
{ context = GetGlobalContext(); }
{ context = GetGlobalContext(); }
else
{
if (typeof Xrm != "undefined") {
// Xrm.Page.context defined within the Xrm.Page object model for form scripts.
context = Xrm.Page.context;
}
else { throw new Error("Context is not available."); }
}
return context.getClientUrl();
}

// Global variables.
var entitiesToDelete = []; // Entity URIs to be deleted (if user chooses to delete sample data)
var deleteData = true; // Delete data by default unless user chooses not to delete.
var clientUrl = Sdk.getClientUrl(); // e.g.: https://org.crm.dynamics.com
var webAPIPath = "/api/data/v8.1"; // Path to the web API.
var account1Uri; // e.g.: Contoso Inc (sample)
var contact1Uri; // e.g.: Yvonne McKey (sample)
var page2Uri; // URI of next page in pagination sample.

// Entity properties to select in a request.


var contactProperties = ["fullname", "jobtitle", "annualincome"];
var accountProperties = ["name"];
var taskProperties = ["subject", "description"];

/**
* @function request
* @description Generic helper function to handle basic XMLHttpRequest calls.
* @param {string} action - The request action. String is case-sensitive.
* @param {string} uri - An absolute or relative URI. Relative URI starts with a "/".
* @param {object} data - An object representing an entity. Required for create and update action.
* @param {boolean} formattedValue - If "true" then include formatted value; "false" otherwise.
* For more info on formatted value, see:
* https://msdn.microsoft.com/library/gg334767.aspx#bkmk_includeFormattedValues
* @param {number} maxPageSize - Indicate the page size. Default is 10 if not defined.
* @returns {Promise} - A Promise that returns either the request object or an error object.
*/
Sdk.request = function (action, uri, data, formattedValue, maxPageSize) {
if (!RegExp(action, "g").test("POST PATCH PUT GET DELETE")) { // Expected action verbs.
throw new Error("Sdk.request: action parameter must be one of the following: " +
"POST, PATCH, PUT, GET, or DELETE.");
}
if (!typeof uri === "string") {
throw new Error("Sdk.request: uri parameter must be a string.");
}
if ((RegExp(action, "g").test("POST PATCH PUT")) && (data === null || data === undefined)) {
throw new Error("Sdk.request: data parameter must not be null for operations that create or modify
data.");
}
if (maxPageSize === null || maxPageSize === undefined) {
maxPageSize = 10; // Default limit is 10 entities per page.
}

// Construct a fully qualified URI if a relative URI is passed in.


if (uri.charAt(0) === "/") {
uri = clientUrl + webAPIPath + uri;
}

return new Promise(function (resolve, reject) {


var request = new XMLHttpRequest();
request.open(action, encodeURI(uri), true);
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
request.setRequestHeader("Prefer", "odata.maxpagesize=" + maxPageSize);
if (formattedValue) {
request.setRequestHeader("Prefer",
"odata.include-annotations=OData.Community.Display.V1.FormattedValue");
"odata.include-annotations=OData.Community.Display.V1.FormattedValue");
}
request.onreadystatechange = function () {
if (this.readyState === 4) {
request.onreadystatechange = null;
switch (this.status) {
case 200: // Success with content returned in response body.
case 204: // Success with no content returned in response body.
resolve(this);
break;
default: // All other statuses are unexpected so are treated like errors.
var error;
try {
error = JSON.parse(request.response).error;
} catch (e) {
error = new Error("Unexpected Error");
}
reject(error);
break;
}
}
};
request.send(JSON.stringify(data));
});
};

/**
* @funnction output
* @description Generic helper function to output data to console.
* @param {array} collection - Array of entities.
* @param {string} label - Text label for what the collection contains.
* @param {array} properties - Array of properties appropriate for the collection.
*/
Sdk.output = function (collection, label, properties) {
console.log(label);
collection.forEach(function (row, i) {
var prop = [];
properties.forEach(function (p) {
var f = p + "@OData.Community.Display.V1.FormattedValue";
prop.push((row[f] ? row[f] : row[p])); // Get formatted value if one exists for this property.
})
console.log("\t%s) %s", i + 1, prop.join(", "));
});
}

/**
* @function startSample
* @description Runs the sample.
* This sample demonstrates basic query operations.
* Results are sent to the debugger's console window.
*/
Sdk.startSample = function () {
// Initializing...
deleteData = document.getElementsByName("removesampledata")[0].checked;
entitiesToDelete = []; //Reset the array.
account1Uri = "";
contact1Uri = "";
page2Uri = "";

console.log("-- Sample started --");


console.log("Create sample data:");
// Add some data to the CRM server so we can query against it.
// Using Deep Insert, we create all the sample data in one request.
// Data structure:
// Accounts
// |--- primarycontactid
// |--- Contact_Tasks (3 tasks)
// |--- Account_Tasks (3 tasks)
// |--- contact_customer_accounts (9 child contacts, each with 3 tasks)
// |--- Contacts
// |--- Contacts
// |--- Contact_Tasks
//
var sampleData = {
"name": "Contoso, Ltd. (sample)",
"primarycontactid": {
"firstname": "Yvonne", "lastname": "McKay (sample)", "jobtitle": "Coffee Master",
"annualincome": 45000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
}, "Account_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
],
"contact_customer_accounts": [
{
"firstname": "Susanna", "lastname": "Stubberod (sample)", "jobtitle": "Senior Purchaser",
"annualincome": 52000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Nancy", "lastname": "Anderson (sample)", "jobtitle": "Activities Manager",
"annualincome": 55500, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Maria", "lastname": "Cambell (sample)", "jobtitle": "Accounts Manager",
"annualincome": 31000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Nancy", "lastname": "Anderson (sample)", "jobtitle": "Logistics Specialist",
"annualincome": 63500, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Scott", "lastname": "Konersmann (sample)", "jobtitle": "Accounts Manager",
"annualincome": 38000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Robert", "lastname": "Lyon (sample)", "jobtitle": "Senior Technician",
"annualincome": 78000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Paul", "lastname": "Cannon (sample)", "jobtitle": "Ski Instructor",
"annualincome": 68500, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Rene", "lastname": "Valdes (sample)", "jobtitle": "Data Analyst III",
"annualincome": 86000, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
},
{
"firstname": "Jim", "lastname": "Glynn (sample)", "jobtitle": "Senior International Sales
Manager",
"annualincome": 81400, "Contact_Tasks": [
{ "subject": "Task 1", "description": "Task 1 description" },
{ "subject": "Task 2", "description": "Task 2 description" },
{ "subject": "Task 3", "description": "Task 3 description" }
]
}
]
};

var uri = "/accounts"; // A relative URI to the account entity.


Sdk.request("POST", uri, sampleData) // Adding sample data so we can query against it.
.then(function (request) {
// Process request.
account1Uri = request.getResponseHeader("OData-EntityId");
entitiesToDelete.push(account1Uri); // To delete later.
console.log("Account 'Contoso, Ltd. (sample)' created with 1 primary contact and 9 associated
contacts.");

// Get primary contact info.


// Most queries are done using this contact.
var uri = account1Uri + "/primarycontactid/$ref"; // Request for the URI only.
return Sdk.request("GET", uri);
})
.then(function (request) {
contact1Uri = JSON.parse(request.response)["@odata.id"];
entitiesToDelete.push(contact1Uri); // To delete later.
console.log("Has primary contact 'Yvonne McKay (sample)' with URI: %s\n", contact1Uri);

// Basic query:
// Query using $select option against a contact entity to get the properties you want.
// For performance best practice, always use $select otherwise all properties are returned.
console.log("-- Basic Query --");
var query = "?$select=" + contactProperties.join(); // Array defined in the global scope.
return Sdk.request("GET", contact1Uri + query, null, true);
})
.then(function (request) {
var contact1 = JSON.parse(request.response);
console.log("Contact basic info:\n\tFullname: '%s'\n\tJobtitle: '%s'\n\tAnnualincome: '%s'
(unformatted)",
contact1.fullname, contact1.jobtitle, contact1.annualincome);
console.log("\tAnnualincome: %s (formatted)\n",
contact1["[email protected]"]);

// Filter criteria:
// Applying filters to get targeted data.
// 1) Using standard query functions (e.g.: contains, endswith, startswith)
// 2) Using CRM query functions (e.g.: LastXhours, Last7Days, Today, Between, In, ...)
// 3) Using filter operators and logical operators (e.g.: eq, ne, gt, and, or, etc…)
// 4) Set precedence using parenthesis (e.g.: ((criteria1) and (criteria2)) or (criteria3)
// For more info, see: https://msdn.microsoft.com/library/gg334767.aspx#bkmk_filter
console.log("-- Filter Criteria --");

// Filter 1: Using standard query functions to filter results.


// In this operation, we will query for all contacts with fullname containing the string "(sample)".
var filter = "&$filter=contains(fullname,'(sample)')";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts filtered by fullname containing '(sample)':", contactProperties);

// Filter 2: Using CRM query functions to filter results.


// In this operation, we will query for all contacts that was created in the last hour.
// For complete list of CRM query functions, see:
// https://msdn.microsoft.com/library/mt607843.aspx
var filter =
"&$filter=Microsoft.Dynamics.CRM.LastXHours(PropertyName='createdon',PropertyValue='1')";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true); // Remember page size limit is set to
10.
})
.then(function(request){
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts that were created within the last 1hr:", contactProperties);

// Filter 3: Using operators


// Building on the previous operation, we will further limit the results by the contact's income.
// For more info on standard filter operators, see:
// https://msdn.microsoft.com/library/gg334767.aspx#bkmk_filter
var filter = "&$filter=contains(fullname,'(sample)') and annualincome gt 55000";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts filtered by fullname and annualincome (<$55,000):",
contactProperties);

// Filter 4: Set precedence using parenthesis.


// Continue building on the previous operation, we will further limit results by job title.
// Parenthesis and the order of filter statements can impact results returned.
var filter = "&$filter=contains(fullname,'(sample)') " +
"and (contains(jobtitle,'senior') or contains(jobtitle,'specialist')) and annualincome gt
55000";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts filtered by fullname, annualincome and jobtitle (Senior or
Specialist):",
contactProperties);

// Order results:
// Filtered results can be order in descending or ascending order.
console.log("\n-- Order Results --");
var filter = "&$filter=contains(fullname,'(sample)') " +
"&$orderby=jobtitle asc, annualincome desc";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts ordered by jobtitle (Ascending) and annualincome (descending):",
contactProperties);

// Parameterized Aliases.
// Aliases can be used as parameters in a query. These parameters can be used in $filter and
$orderby options.
// Using the previous operation as basis, parameterizing the query will give us the same results.
// For more info, see:
https://msdn.microsoft.com/library/gg309638.aspx#bkmk_passParametersToFunctions
console.log("\n-- Parameterized Aliases --");
var filter = "&$filter=contains(@p1,'(sample)') " +
"&$orderby=@p2 asc, @p3 desc&@p1=fullname&@p2=jobtitle&@p3=annualincome";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts list using parameterized aliases:", contactProperties);

// Limit records returned.


// To further limit the records returned, use the $top query option.
// Specifying a limit number for $top will return at most that number of results per request.
// Extra results are ignored.
console.log("\n-- Top Results --");
var filter = "&$filter=contains(fullname,'(sample)')&$top=5";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts top 5 results:", contactProperties);

// Result count.
// Count the number of results matching the filter criteria.
// 1) Get a count of a collection without the data.
// 2) Get a count along with the data.
// HINT: Use count together with the "odata.maxpagesize" to calculate the number of pages in the
query.
// NOTE: CRM has a max record limit of 5000 records per response.
console.log("\n-- Result Count --");
return Sdk.request("GET", "/contacts/$count"); // Count is returned in response body.
})
.then(function (request) {
console.log("The contacts collection has %s contacts.", request.response); // Count maximum is 5000.

// 2) Get filtered result with a count


var filter = "&$filter=contains(jobtitle,'senior') or contains(jobtitle, 'manager')&$count=true";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true);
})
.then(function (request) {
var count = JSON.parse(request.response)["@odata.count"];
console.log("%s contacts have either 'Manager' or 'Senior' designation in their jobtitle.", count);
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Manager or Senior:", contactProperties);

// Pagination:
// For large data sets, you can limit the number of records returned per page.
// Then offer a "next page" and "previous page" links for users to browse through all the data.
// NOTE: This is why you should not use $top with maxpagesize. $top will limit results returned
// preventing you from accessing all possible results in the query.
// For example: If your query has 10 entities in the result and you limit your result to
$top=5
// then, you can't get to the remaining 5 results; but with "maxpagesize" (without $top), you
can.
// HINT: Save the URI of the current page so users can go "next" and "previous".
console.log("\n-- Pagination --");
var filter = "&$filter=contains(fullname,'(sample)')&$count=true";
var query = "?$select=" + contactProperties.join() + filter;
return Sdk.request("GET", "/contacts" + query, null, true, 4); // 4 records per page.
})
.then(function (request) {
var count = JSON.parse(request.response)["@odata.count"];
var maxpages = Math.ceil(count / 4);
console.log("Contacts total: %s \tContacts per page: %s.\tOutputting first 2 pages.", count, 4);
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Page 1 of " + maxpages + ":", contactProperties);
Sdk.output(collection, "Page 1 of " + maxpages + ":", contactProperties);

// Getting the next page.


page2Uri = JSON.parse(request.response)["@odata.nextLink"]; // This URI is already encoded.
return Sdk.request("GET", decodeURI(page2Uri), null, true, 4); // URI re-encoded in the request
function.
})
.then(function (request) {
var count = JSON.parse(request.response)["@odata.count"];
var maxpages = Math.ceil(count / 4);
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Page 2 of " + maxpages + ":", contactProperties);

// Using expand option to retrieve additional information.


// It is common for entities to have associations with other entities in the system and you might
want
// to also retrieve this information in the same request. To retrieve information on associated
entities,
// use the $expand query option on navigation properties.
// 1) Expand using single-valued navigation properties (e.g.: via the 'primarycontactid')
// 2) Expand using partner property (e.g.: from contact to account via the
'account_primary_contact')
// 3) Expand using collection-valued navigation properties (e.g.: via the
'contact_customer_accounts')
// 4) Expand using multiple navigation property types in a single request.
// NOTE: Expansions can only go 1 level deep.
// For performance best practice, always use $select statement in an expand option.
console.log("\n-- Expanding Results --");

// 1) Expand using single-valued navigation properties (e.g.: via the 'primarycontactid')


var expand = "&$expand=primarycontactid($select=" + contactProperties.join() + ")";
var query = "?$select=" + accountProperties.join() + expand;
return Sdk.request("GET", account1Uri + query, null, true);
})
.then(function (request) {
var account = JSON.parse(request.response);
var str = "Account '%s' has the following primary contact person:\n\t" +
"Fullname: '%s' \n\tJobtitle: '%s' \n\tAnnualincome: '%s'";
console.log(str, account.name,
account.primarycontactid.fullname,
account.primarycontactid.jobtitle,
account.primarycontactid.annualincome);

// 2) Expand using partner property (e.g.: from contact to account via the
'account_primary_contact')
var expand = "&$expand=account_primary_contact($select=" + accountProperties.join() + ")";
var query = "?$select=" + contactProperties.join() + expand;
return Sdk.request("GET", contact1Uri + query, null, true);
})
.then(function (request) {
var contact = JSON.parse(request.response);
var label = "Contact '" + contact.fullname + "' is the primary contact for the following accounts:";
Sdk.output(contact.account_primary_contact, label, accountProperties);

// 3) Expand using collection-valued navigation properties (e.g.: via the


'contact_customer_accounts')
var expand = "&$expand=contact_customer_accounts($select=" + contactProperties.join() + ")"
var query = "?$select=" + accountProperties.join() + expand;
return Sdk.request("GET", account1Uri + query, null, true);
})
.then(function (request) {
var account = JSON.parse(request.response);
var label = "Account '" + account.name + "' has the following contact customers:";
var collection = account.contact_customer_accounts;
Sdk.output(collection, label, contactProperties);

// 4) Expand using multiple navigation property types in a single request.


// For example: expanding on primiarycontactid, contact_customer_accounts, and Account_Tasks.
console.log("\n-- Expanding multiple property types in one request -- ");
var expand = "&$expand=primarycontactid($select=" + contactProperties.join() + ")," +
var expand = "&$expand=primarycontactid($select=" + contactProperties.join() + ")," +
"contact_customer_accounts($select=" + contactProperties.join() + ")," +
"Account_Tasks($select=" + taskProperties.join() + ")";
var query = "?$select=" + accountProperties.join() + expand;
return Sdk.request("GET", account1Uri + query, null, true);
})
.then(function (request) {
var account = JSON.parse(request.response);
var label = "Account '%s' has the following primary contact person:\n\t" +
"Fullname: '%s' \n\tJobtitle: '%s' \n\tAnnualincome: '%s'";
console.log(label, account.name,
account.primarycontactid.fullname,
account.primarycontactid.jobtitle,
account.primarycontactid.annualincome);

// Handling each collection separately.


label = "Account '" + account.name + "' has the following related contacts:";
var collection = account.contact_customer_accounts;
Sdk.output(collection, label, contactProperties);

label = "Account '" + account.name + "' has the following tasks:";


collection = account.Account_Tasks;
Sdk.output(collection, label, taskProperties);

// FetchXML
// Using FetchXML to query for all contacts whose fullname contains '(sample)'.
// NOTE: XML string must be URI encoded.
// For more information, see: https://msdn.microsoft.com/library/gg328117.aspx
console.log("\n-- FetchXML -- ");
var fetchXML = "<fetch mapping=\"logical\" output-format=\"xml-platform\" version=\"1.0\"
distinct=\"false\"> \
<entity name=\"contact\"> \
<attribute name=\"fullname\" /> \
<attribute name=\"jobtitle\" /> \
<attribute name=\"annualincome\" /> \
<order descending=\"true\" attribute=\"fullname\" /> \
<filter type=\"and\"> \
<condition value=\"%(sample)%\" attribute=\"fullname\" operator=\"like\" /> \
</filter> \
</entity> \
</fetch> ";
return Sdk.request("GET", "/contacts?fetchXml=" + encodeURIComponent(fetchXML), null, true);
})
.then(function(request){
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Contacts Fetched by fullname containing '(sample)':", contactProperties);

// FetchXML pagination.
// Noticed the attribute "page=3" and "count=4" in this XML.
// We want to retrieve entities in page 3 but limit results to only 4 entities.
// If the result return zero records for the page, that means we have reached the end of the result
set.
// For more info, see: https://msdn.microsoft.com/library/mt607533.aspx#bkmk_useFetchXML
var fetchXML = "<fetch mapping=\"logical\" output-format=\"xml-platform\" version=\"1.0\" \
distinct=\"false\" page=\"3\" count=\"4\"> \
<entity name=\"contact\"> \
<attribute name=\"fullname\" /> \
<attribute name=\"jobtitle\" /> \
<attribute name=\"annualincome\" /> \
<order descending=\"true\" attribute=\"fullname\" /> \
<filter type=\"and\"> \
<condition value=\"%(sample)%\" attribute=\"fullname\" operator=\"like\" /> \
</filter> \
</entity> \
</fetch> ";
return Sdk.request("GET", "/contacts?fetchXml=" + encodeURIComponent(fetchXML), null, true);
})
.then(function(request){
var collection = JSON.parse(request.response).value;
if (collection.length == 0) {
if (collection.length == 0) {
console.log("There are no records on this page."); // We have reached the end of our query
result set.
} else {
Sdk.output(collection, "Contacts Fetched by fullname containing '(sample)' - Page 3:",
contactProperties);
}

// Using predefined queries.


// 1) Saved query
// 2) User query
// For more info, see:
// https://msdn.microsoft.com/library/mt607533.aspx

// Saved Query
// Get the Saved Query "Active Accounts" and display results to output.
console.log("\n-- Saved Query -- ");
var filter = "&$filter=name eq 'Active Accounts'";
var query = "?$select=name,savedqueryid" + filter;
return Sdk.request("GET", "/savedqueries" + query, null, true); // Requesting for saved query GUID.
})
.then(function(request){
// Get the savedqueryid GUID and then use it to request for the entities in that query.
var activeAccount = JSON.parse(request.response).value[0]; // Get the first matched.
var savedqueryid = activeAccount.savedqueryid;

// Request for the saved query results


return Sdk.request("GET", "/accounts?savedQuery=" + savedqueryid, null, true);
})
.then (function (request){
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Saved Query (Active Accounts):", accountProperties);

// User Query
// Create a user query then get it from the server and execute that query for results.
// For more info, see: https://msdn.microsoft.com/library/gg509053.aspx
console.log("\n-- User Query -- ");
var userquery = {
"name": "My User Query",
"description": "User query to display contact info.",
"querytype": 0,
"returnedtypecode": "contact",
"fetchxml": "<fetch mapping=\"logical\" output-format=\"xml-platform\" version=\"1.0\"
distinct=\"false\"> \
<entity name=\"contact\"> \
<attribute name=\"fullname\" /> \
<attribute name=\"contactid\" /> \
<attribute name=\"jobtitle\" /> \
<attribute name=\"annualincome\" /> \
<order descending=\"false\" attribute=\"fullname\" /> \
<filter type=\"and\"> \
<condition value=\"%(sample)%\" attribute=\"fullname\" operator=\"like\" /> \
<condition value=\"%Manager%\" attribute=\"jobtitle\" operator=\"like\" /> \
<condition value=\"55000\" attribute=\"annualincome\" operator=\"gt\" /> \
</filter> \
</entity> \
</fetch> "
};

return Sdk.request("POST", "/userqueries", userquery, true); // Create the user query.


})
.then(function (request){
// Look up the user query we just created
// then use it to request for the entities in that query.
var filter = "&$filter=name eq 'My User Query'";
var query = "?$select=name,userqueryid," + filter;
return Sdk.request("GET", "/userqueries" + query, null, true);
})
.then(function (request) {
var userQuery = JSON.parse(request.response).value[0]; // Get the first matched.
var userQuery = JSON.parse(request.response).value[0]; // Get the first matched.
var userqueryid = userQuery.userqueryid;
entitiesToDelete.push(clientUrl + webAPIPath + "/userqueries(" + userqueryid + ")");

// Request for the user query results


return Sdk.request("GET", "/contacts?userQuery=" + userqueryid, null, true);
})
.then(function (request) {
var collection = JSON.parse(request.response).value;
Sdk.output(collection, "Saved User Query:", contactProperties);

// House cleaning - deleting sample data


// For more info on cascading delete, see:
// https://msdn.microsoft.com/library/gg309412.aspx#BKMK_CascadingBehavior
console.log("\n-- Deleting Sample Data --");
if (deleteData) {
for (var i = 0; i < entitiesToDelete.length; i++) {
console.log("Deleting entity: " + entitiesToDelete[i]);
Sdk.request("DELETE", entitiesToDelete[i], null)
.catch(function (err) {
console.log("ERROR: Delete failed --Reason: \n\t" + err.message);
});
}
} else {
console.log("Sample data not deleted.");
}
})
.catch(function (error) {
console.log(error.message);
});

See also
Use the Dataverse Web API
Query Data using the Web API
Web API Samples
Web API Query Data Sample
Web API Query Data Sample (C#)
Web API Samples (Client-side JavaScript)
Web API Basic Operations Sample (Client-side JavaScript)
Web API Conditional Operations Sample (Client-side JavaScript)
Web API Functions and Actions Sample (Client-side JavaScript)

You might also like