Almanac
Microsoft/dataversePower Platform

Consultant KB for Microsoft Dataverse, the data layer under Dynamics 365 and the Power Platform: data model, security model, business logic, APIs and integration, search and queries, analytics and Fabric, ALM and solutions, administration, governance and compliance, and Dataverse as an agent data platform, plus cross-cutting decision guides. Implementation notes, configuration decisions and the gotchas that bite on real projects. Populated by the daily author agent from the Dataverse release plans, docs repo and product blog, plus the author's own consultant notes.

feature-web-api-and-odata.mdv1 · history
CurrentApplies to DeveloperUpdated last monthSource Microsoft Learn

What it does

The RESTful HTTP endpoint for Dataverse data and table definitions, implementing OData v4. It sits at [Organization URI]/api/data/v9.2/ and covers everything the Organization service does, presented as entity sets, functions and actions rather than SOAP messages.

Key facts

  • Built on the OASIS OData v4 standard, so any OData v4 client library works. Aside from the Python SDK, Microsoft doesn't ship language-specific assemblies for it — you compose HTTP requests or use a third-party library.
  • CRUD maps to HTTP verbs: GET, POST, PATCH, DELETE. Other messages surface as OData functions (GET, no data change) or actions (POST, parameters in the body).
  • Everything ultimately goes through the Organization service. The Web API is a different presentation of the same platform, and the same event framework messages fire either way.
  • Authentication is OAuth 2.0 with Microsoft Entra ID as the identity provider, via MSAL. Public clients use a scope of <environment-url>/user_impersonation; confidential clients use <environment-url>/.default.
  • Access tokens expire in about an hour by default, so a real client needs a refresh strategy — the documented pattern is a DelegatingHandler that reacquires the token per request.
  • Server-to-server calls use an application user bound to an Entra app registration with a client secret or certificate. That account doesn't consume a paid licence.
  • Query options behave as OData expects: $select, $filter, $orderby, $expand, $top, $count. Optimistic concurrency uses @odata.etag with If-Match.
  • Model-driven apps expose Xrm.WebApi, PCF components expose a WebAPI object, and Power Pages exposes a Portals Web API with a subset of the operations.
  • Batch requests go to $batch and allow a URL of up to 64 KB inside the body, which is the standard escape hatch when a query URL gets too long.

When to use / skip

Use it for anything that isn't .NET, anything running in a browser, anything where you don't want a NuGet dependency, and anything called from a script or a pipeline. It's the default for integration work in 2026. Reach for the SDK for .NET instead when you're writing plug-ins, when you want strongly typed early-bound classes, or when you specifically need ExecuteTransaction semantics without composing multipart batch payloads by hand. For everything else the smaller JSON payload and the absence of a client library dependency win.

Configuration decisions

  • Delegated user permissions or an application user with a service principal, which decides whose privileges apply and whose name appears in audit.
  • Which security role the application user gets, built as a custom role scoped to what the integration actually touches rather than System Administrator.
  • Secret or certificate for the app registration, and where the credential lives — Key Vault or nothing.
  • Whether the client returns created data with Prefer: return=representation or takes the OData-EntityId header and moves on.
  • Whether you pin the API version in the URL and how you handle version drift over the life of the integration.

Gotchas

  • Entity set names aren't logical names. It's accounts and contacts, but plurals are irregular enough that you should read $metadata rather than guess.
  • Setting a lookup uses the @odata.bind annotation with the navigation property name, not the lookup column's logical name. Getting this wrong produces an error that names a property nobody can find.
  • The maximum URL length is 32 KB for a GET, which sounds generous until someone writes a FetchXML query with a long filter. Move it into a $batch body.
  • Formatted values, lookup logical names and display names only appear when you ask for them with Prefer: odata.include-annotations. Clients that assume they're always present break the moment they hit a different environment.
  • Headers set on a $batch request don't apply to the items inside it. Every item needs its own headers, which is a reliably confusing hour for someone.
  • The token expiring mid-run is the classic long-integration failure. A working proof of concept that dies after 55 minutes has almost always got a single AcquireToken call in it.

Consultant notes

  • Insist on an application user with a scoped custom security role for every integration, named after the integration. "Which system changed this row?" needs to be answerable from the audit log.
  • Client secrets expire. Put the expiry in whatever the client uses for renewals calendar on the day you create it, because a silently expired secret at 2am is the worst kind of outage.
  • Warn the client that the Web API applies the same security model as the UI. An integration that can read everything is a decision someone made, not a platform default.
  • If the client's integration partner asks for a shared System Administrator account, treat that as a red flag and offer them an application user instead. It costs nothing and removes a licence as well as a risk.

Worth another look when the API version moves past v9.2, or when the client's app registration secrets come up for renewal.

Was this accurate?