Batch operations in the Dataverse Web API
How to make multiple Dataverse Web API calls in one HTTP round-trip — $batch requests, change sets, and the performance gains at scale.
Calling Dataverse one record at a time over the Web API is fine for occasional operations. For integrations processing hundreds or thousands of records, the per-call HTTP overhead dominates total time. Batch operations — submitting multiple operations in a single HTTP request — collapse that overhead and unlock substantially higher throughput.
The OData batch endpoint. The Web API exposes a batch endpoint at POST /api/data/v9.x/$batch. The request body is a multipart payload containing many sub-requests; the response is a multipart payload with each sub-response.
Anatomy of a batch request.
POST /api/data/v9.2/$batch HTTP/1.1
Content-Type: multipart/mixed; boundary=batch_AAA
--batch_AAA
Content-Type: application/http
Content-Transfer-Encoding: binary
GET /api/data/v9.2/accounts(GUID-1) HTTP/1.1
Accept: application/json
--batch_AAA
Content-Type: application/http
Content-Transfer-Encoding: binary
GET /api/data/v9.2/accounts(GUID-2) HTTP/1.1
Accept: application/json
--batch_AAA--
Multiple GETs in one HTTP round-trip; the server processes each, responses bundle into the multipart reply.
Mixing operation types. Within one batch, mix GETs, POSTs, PATCHes, DELETEs. For non-GET operations, wrap them in a change set for transactional atomicity (see below).
Change sets — atomic groups within a batch.
A change set is a sub-group within a batch that's processed atomically — either all sub-requests succeed or all are rolled back. Used for related writes that must succeed together:
--batch_AAA
Content-Type: multipart/mixed; boundary=changeset_BBB
--changeset_BBB
Content-Type: application/http
Content-Transfer-Encoding: binary
POST /api/data/v9.2/accounts HTTP/1.1
Content-Type: application/json
{
"name": "Contoso"
}
--changeset_BBB
Content-Type: application/http
Content-Transfer-Encoding: binary
POST /api/data/v9.2/contacts HTTP/1.1
Content-Type: application/json
{
"firstname": "Karen",
"_parentcustomerid_value": "$1"
}
--changeset_BBB--
--batch_AAA--
The contact creation references the account via the $1 placeholder — the platform substitutes the newly-created account's GUID in the second request. If either fails, both roll back.
Performance characteristics.
- Network round-trip count drops from N to 1. Latency reduces dramatically for chatty integrations.
- Throughput increases — batch of 100 records typically completes in seconds vs minutes.
- Server processing — each sub-request still executes server-side individually; CPU savings are modest.
- Throttling — batch requests count toward Dataverse API limits as multiple requests (one per sub-request).
Practical limits.
- Maximum sub-requests per batch — typically ~1000 per batch.
- Maximum change sets per batch — typically ~10.
- Maximum sub-requests per change set — typically ~100.
- Maximum payload size — typically several megabytes; very large attachments hit limits.
Check Dataverse documentation for current values; they evolve.
Common patterns.
- Bulk upsert — submit 100 PATCHes-with-create-if-missing in one batch.
- Related-record creation — create an Account plus 10 Contacts in one batch with a change set, ensuring atomicity.
- Read-many-by-ID — submit 100 GETs in one batch instead of 100 round-trips.
- Mass deletion — submit 100 DELETEs in one batch.
- Read followed by conditional write — a batch with a GET sub-request whose result drives a conditional PATCH in the same batch.
SDK support. The Dataverse SDK for .NET (OrganizationServiceProxy, ServiceClient) supports batch operations through ExecuteMultipleRequest — the SDK equivalent of the Web API's batch endpoint. The mechanics differ slightly but the pattern is the same.
Power Automate. Power Automate's "List rows" and other Dataverse actions don't natively use batch internally for bulk operations. To use batching from Power Automate, call the Web API directly via the HTTP action with a multipart body — more code, but substantial performance gain for bulk scenarios.
Common pitfalls.
- Throttling surprise — a batch with 100 sub-requests consumes 100 API call units, not 1. Plan capacity.
- Mixing transactional and non-transactional in one batch — change sets vs free sub-requests have different rollback semantics. Be deliberate.
- Batch larger than the limit — exceeds maximum; gets rejected. Split.
- Error handling — partial success — non-change-set sub-requests can independently succeed or fail; the consumer must process the response carefully.
Operational reality. For any Dataverse integration handling more than dozens of records, batching is the table-stakes optimisation. The investment is small at design time; the throughput gain compounds over the integration's life.
Related guides
- Azure API Management in front of DataverseHow API Management acts as a façade for Dynamics 365 APIs — rate limiting, authentication, transformation, observability, and developer portal — and why it matters at scale.
- FetchXML vs OData in DataverseTwo query languages for Dataverse — what each does, performance and capability differences, and when to choose which.
- Microsoft Graph and DataverseHow Microsoft Graph connects Microsoft 365 data to Dynamics 365 — Graph connectors, indexing Dataverse content, and the Copilot enablement story.
- Microsoft Graph API with Business CentralHow Business Central exposes data through Microsoft Graph — the new unified API surface, what's available, authentication, and where Graph fits alongside BC's native APIs.
- B2C authentication with Dynamics 365 — Entra External ID and beyondHow to authenticate external customers and partners against Dynamics 365 — Entra External ID (formerly Azure AD B2C), Power Pages authentication, and the patterns for B2C identity in CRM and ERP.