7.1 Dataverse Web API Operations & OAuth Authentication
Key Takeaways
- The Dataverse Web API is an OData v4 REST/JSON interface; entity sets are the pluralized logical name of each table.
- $select, $filter, $expand, $orderby, and $top are the core query options; $batch groups multiple operations, with changesets making grouped writes atomic.
- Functions (GET, read-only) and Actions (POST, side effects) can be unbound or bound; custom APIs are exposed through this same mechanism.
- FetchXML remains available via the fetchXml query parameter for queries too complex for OData syntax, such as multi-level aggregation.
- All Web API calls require an OAuth 2.0 bearer token from Microsoft Entra ID: delegated (authorization code) for user-context calls, client credentials for unattended app-only calls via an Application User.
The Dataverse Web API is the primary integration surface for PL-400 developers: it is what canvas apps, custom connectors, PCF components, Azure Functions, and third-party applications use to read and write Dataverse data over plain HTTPS. Knowing how to construct efficient OData v4 requests and how to authenticate those requests with OAuth 2.0 is foundational to nearly every "Extend the platform" scenario on the exam.
OData v4 Fundamentals
The Web API implements the OData v4 protocol over REST, returning JSON. Every table is exposed as an entity set named by the pluralized logical name — the account table becomes accounts, contact becomes contacts. The base address follows the pattern https://orgname.crm.dynamics.com/api/data/v9.2/.
CRUD operations map to standard HTTP verbs:
| Operation | HTTP Verb | Example |
|---|---|---|
| Create | POST | POST /accounts with a JSON body |
| Retrieve | GET | GET /accounts(guid) |
| Update | PATCH | PATCH /accounts(guid) with changed fields only |
| Delete | DELETE | DELETE /accounts(guid) |
| Associate/Disassociate | POST/DELETE | POST /accounts(guid)/contact_customer_accounts/$ref |
A POST that creates a record returns 204 No Content with an OData-EntityId header by default; adding a Prefer: return=representation header returns the full created record body instead, saving a follow-up GET.
Querying with System Query Options
The Web API supports the standard OData query options, all of which appear on the exam:
$select— restrict the columns returned (always specify this; never pull every column)$filter— server-side row filtering, e.g.$filter=revenue gt 1000000 and statecode eq 0$expand— pull related records in the same round trip, for both single-valued (primarycontactid($select=fullname)) and collection-valued navigation properties (contact_customer_accounts)$orderby— sort results, e.g.$orderby=createdon desc$topand$count— limit rows and request a total count$apply— aggregation/grouping (OData's answer to SQLGROUP BY)
A combined example: GET /accounts?$select=name,revenue&$filter=statecode eq 0&$expand=primarycontactid($select=fullname)&$orderby=revenue desc&$top=25.
Batch Requests
For scenarios that need many operations in one network call — bulk creates, or several dependent writes that must be atomic — the Web API exposes a $batch endpoint. A batch request is a multipart/mixed payload; operations that must succeed or fail together are grouped into a changeset, while independent GET requests can sit outside a changeset. Batching reduces round trips and is the Web API equivalent of the Organization service's ExecuteMultipleRequest.
Actions and Functions
Beyond plain CRUD, Dataverse exposes custom server-side logic through two OData constructs:
- Functions are read-only, invoked with
GET, and can be unbound (global, likeWhoAmI()) or bound to an entity or entity collection. - Actions perform side effects, are invoked with
POST, and are similarly unbound or bound. A custom API configured as an action or function is exactly what surfaces here — once registered, it is called from the Web API the same way as any built-in action.
When a query is too complex for OData syntax — multi-level aggregation, complex joins, or paging behaviors only FetchXML supports — the Web API still accepts it via the fetchXml query parameter: GET /accounts?fetchXml=<fetch>...</fetch>.
Authenticating with OAuth 2.0
Every Web API call requires a valid OAuth 2.0 bearer token issued by Microsoft Entra ID; there is no anonymous or basic-auth path. This starts with an app registration in Entra ID, which yields an Application (client) ID and a Directory (tenant) ID, plus either a client secret or a certificate for authenticating the app itself.
Two authentication patterns cover almost every PL-400 scenario:
| Pattern | Flow | Identity context | Typical caller |
|---|---|---|---|
| Delegated | Authorization code (or device code) | Runs as the signed-in user, respects their security roles | Interactive apps, PCF context.webAPI calls |
| Application (app-only) | Client credentials | Runs as an Application User with its own security role, no signed-in user | Azure Functions, unattended services, custom connectors |
For app-only calls, the app registration must be added to the target environment as an Application User and assigned a security role — the client ID alone grants no Dataverse permissions. The token request targets the resource https://orgname.crm.dynamics.com/.default, and the resulting token is sent as Authorization: Bearer <token>. The Microsoft Authentication Library (MSAL) is the supported library for acquiring these tokens in both .NET and JavaScript/Node code, handling token caching and refresh so custom code doesn't have to.
A Practical Token Request
An app-only (client credentials) token request against the Microsoft identity platform's /token endpoint looks like this in outline:
POST https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id={applicationId}
&client_secret={clientSecret}
&scope=https://orgname.crm.dynamics.com/.default
The response contains an access_token (a signed JWT) and an expires_in value in seconds — typically around one hour. Code should treat that token as opaque and short-lived: cache it for reuse across calls within its lifetime, but always be ready to request a fresh one on expiry rather than hard-coding a token value. MSAL's token cache handles this automatically for both delegated and app-only flows, which is why hand-rolling raw HTTP token requests is discouraged outside of learning exercises.
Versioning and Stability
The Web API is versioned in its URL path (v9.1, v9.2, and so on). Microsoft recommends always pinning to an explicit version rather than relying on an unversioned alias, since behavior can change between major versions and an unpinned integration could break unexpectedly after a platform update. For PL-400 purposes, know that $select, $filter, $expand, batch requests, and the actions/functions model are stable, well-documented surface area that has existed across all current supported versions — the version number mainly reflects which release of Dataverse's schema and message set is being targeted.
A developer needs to retrieve an account record together with its primary contact's full name in a single Web API call. Which approach is correct?
Which OAuth 2.0 pattern should an unattended Azure Function use to authenticate to Dataverse with no signed-in user present?