Compose a multipart/mixed request body with a unique batch boundary string; each individual operation is a MIME part separated by --batch_<boundary>.
For non-atomic read or write operations, add each individual request as a separate MIME part with its own Content-Type: application/http and Content-Transfer-Encoding: binary headers followed by the raw HTTP request line and body.
To make multiple writes atomic (all succeed or all fail), group them inside a changeset part: use --changeset_<boundary> sub-parts within a Content-Type: multipart/mixed; boundary=changeset_<boundary> parent part.
POST the assembled batch to https://<org>.crm.dynamics.com/api/data/v9.2/$batch with Content-Type: multipart/mixed; boundary=batch_<boundary>.
Parse the multipart response body; each sub-response contains its own HTTP status code — a changeset either fully succeeds (all 204s) or fully fails (the changeset part returns a single error response).
Reference the result of an earlier operation within the same batch using the Content-ID header: set Content-ID: 1 on a create operation, then reference it as $1 in a subsequent operation's URL within the same changeset.
Known gotchas
GET requests cannot be included inside a changeset (changesets are write-only); GETs must appear as standalone MIME parts in the batch outside any changeset.
The batch endpoint itself does not return 4xx if individual parts fail — check each sub-response status code independently, as a failed non-changeset part does not abort the rest of the batch.
Total batch request size is limited (typically a few MB); split very large batches into multiple $batch calls and implement retry logic for failed changesets.
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp