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
DelegatingHandlerthat 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.etagwithIf-Match. - Model-driven apps expose
Xrm.WebApi, PCF components expose aWebAPIobject, and Power Pages exposes a Portals Web API with a subset of the operations. - Batch requests go to
$batchand 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=representationor takes theOData-EntityIdheader 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
accountsandcontacts, but plurals are irregular enough that you should read$metadatarather than guess. - Setting a lookup uses the
@odata.bindannotation 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
$batchbody. - 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
$batchrequest 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
AcquireTokencall 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.