[object Object]

Dataverse looks unlimited until you scale. Then you discover the API service protection limits, the throttle responses, and the difference between “user limit” and “tenant limit.” Here are the ones you will hit and how to engineer around them.

The three protection limits

  1. Number of requests per user per 5-minute sliding window (default: 6,000).
  2. Total execution time per user per 5-minute window (default: 1,200 seconds).
  3. Number of concurrent requests per user (default: 52).

Hit any of these and you get HTTP 429 with Retry-After headers. Your code must respect them.

The headers that tell you the truth

Every response carries:

  • x-ms-ratelimit-burst-remaining-xrm-requests
  • x-ms-ratelimit-time-remaining-xrm-requests

Read them. Log them. Throttle yourself before the platform does.

const r = await fetch(url);
const burst = r.headers.get('x-ms-ratelimit-burst-remaining-xrm-requests');
if (burst < 100) await sleep(2000); // back off proactively

Application user vs interactive user

Service principals (application users) get the same limits as interactive users by default. A long-running integration that uses a single application user will hit the per-user limit fast. Solutions:

  • Use multiple application users for different integration workloads.
  • Use the Dataverse Search API for read-heavy workloads (different limits).
  • Use ExecuteMultiple to batch up to 1,000 operations as one request, drastically reducing the request count.

ExecuteMultiple: the batching superpower

ExecuteMultiple lets you send up to 1,000 OrganizationRequest operations in a single API call. Each operation still counts against execution time but only one against the request limit. For migrations, batch updates, and bulk imports, this is the difference between days and hours.

var multi = new ExecuteMultipleRequest {
    Settings = new ExecuteMultipleSettings { ContinueOnError = true, ReturnResponses = false },
    Requests = new OrganizationRequestCollection()
};
foreach (var record in batch) {
    multi.Requests.Add(new UpdateRequest { Target = record });
}
service.Execute(multi);

The Dataverse Search detour

Dataverse Search has its own quota separate from the main API. For free-text or fuzzy lookup needs, use Search instead of RetrieveMultiple with like filters. Faster, cheaper, separately quota’d.

Power Automate is a special case

Power Automate flows that update Dataverse share the limits of whichever connection is configured. A connection bound to a regular user account will throttle that user’s interactive sessions. Always bind production flows to a service principal.

The 2-minute plugin timeout

Plugins running in sandbox have a 2-minute hard limit. Bulk operations inside plugins blow past this. Move bulk work to a Power Automate flow or an Azure Function triggered by the plugin (fire-and-forget pattern).

What to do this week

Add throttle-header logging to your highest-volume integration. Run for a week. If your burst-remaining ever drops below 1,000, you are one bad day away from production throttling. Architect the batching now.

[object Object]
Share