A growing B2B merchant no longer maintains the catalogue by hand: items, prices and stock live in the ERP and travel into the shop and back every night through a bulk import. This nightly run tends to fail at the same point as soon as the data volume grows, namely on the rate limits of the cloud ERP. Microsoft Dataverse, for example, allows 6,000 requests (Microsoft Learn) per sliding five-minute window; exceed them and you get HTTP 429 (RFC 6585) and an aborted partial import. The result in the morning is contradictory prices, incorrect availability and a data state that has to be reconciled by hand. This article shows why the throttling kicks in and how batching, deliberate concurrency control and clean Retry-After handling make connecting Dynamics 365 and SAP to the shop reliable.
Key takeaways
- Microsoft Dataverse limits each user to 6,000 requests, 1,200 seconds of combined execution time and 52 concurrent requests per 300-second window (Microsoft Learn). Reach any limit and the service responds with HTTP 429 (RFC 6585).
- The 429 response carries a Retry-After header with the wait time in seconds (Microsoft Learn). Ignoring it and resending immediately prolongs the throttling instead of ending it.
- Batching bundles many records into few requests: a single OData $batch holds up to 1,000 operations (Microsoft Learn) and cuts the request volume sharply.
- Dynamics 365 Finance and Operations throttles by priority; low-priority integrations are throttled first (Microsoft Learn). SAP does not publish one uniform limit but throttles per API and tenant (SAP Knowledge Base).
- Against aborted runs, exponential backoff with jitter (AWS), idempotent writes and resume points ensure that an interrupted import continues without duplicates.
Why Nightly Bulk Imports Fail on Rate Limits
The classic mistake is an import that sends as fast as it can. A script reads 40,000 items from the ERP and fires an individual write request for each one at the cloud interface. In a test environment with a few hundred records this runs through; in production with the full catalogue it hits the limit after a short while. Cloud ERP systems protect themselves with so-called service protection limits so that a single tenant cannot overload the shared infrastructure. Microsoft Dataverse caps each user at 6,000 requests per five-minute window of 300 seconds (Microsoft Learn). Once the number is reached, there is no slower response, only a hard HTTP 429 (RFC 6585): the request is rejected.
The real problem is not the limit itself but how it is handled. An import without error handling treats the 429 as a crash and aborts. What remains is a half-transferred data state: the first thousand prices are updated, the rest still reflect yesterday. With a Dynamics 365 connection via OData and the Data Management Framework this stands out especially, because price and stock data are tightly coupled. If you plan for the limit instead, you turn it from a source of errors into a predictable cadence: the import takes a little longer but runs through completely and repeatably.
What a Service Protection Limit Actually Means
A service protection limit is not a single threshold but a bundle of several limits that apply in parallel. With Microsoft Dataverse there are three: the number of requests, the combined execution time and the number of concurrent connections. All three are measured over a sliding window of 300 seconds (Microsoft Learn). Reaching any one of the three is enough to be throttled. Execution time in particular is often overlooked: even a few but very compute-heavy requests can exhaust the 1,200 seconds of combined execution time per window (Microsoft Learn) long before the raw request count becomes critical.
Request Count
6,000 requests per 300-second window in Dataverse (Microsoft Learn). Every read and write counts individually.
Execution Time
1,200 seconds of combined execution time per window (Microsoft Learn). Heavy queries eat into this the most.
Concurrency
52 concurrent requests as the ceiling (Microsoft Learn). More parallel connections trigger 429 responses at once.
HTTP 429
On exceeding a limit the service responds with 429 Too Many Requests (RFC 6585) and a Retry-After header.
Priority Tiers
Dynamics 365 Finance and Operations throttles by priority; low-priority integrations are throttled first (Microsoft Learn).
Purpose of the Limit
The limits protect the shared cloud infrastructure from overload by a single tenant and keep it stable for everyone.
Reading HTTP 429 and the Retry-After Header Correctly
The decisive building block sits in the response itself. Along with the status 429, the service delivers a Retry-After header that states in seconds how long the caller should wait before resending the request (Microsoft Learn). This value is not a guideline but an instruction from the server. A robust import reads it, pauses exactly that long and only then retries. Importantly, resubmitted requests are treated like new requests after the interval and receive no higher priority (Microsoft Learn). An immediate, unthrottled retry therefore only makes things worse.
Retry-After Beats Any Guess
send batch to cloud ERP
if response == 429:
wait Retry-After seconds (from header)
retry the same batch (idempotent)
else if response == 5xx:
backoff = min(cap, base * 2^attempt) + random(jitter)
wait backoff, retry (max 3 to 5 attempts)
else:
mark batch as transferred
store resume pointBatching: Many Records, Few Requests
The most effective lever against the request limit is not to generate so many requests in the first place. Instead of sending a separate call for every item, batching bundles many operations into a single request. An OData $batch in Dataverse holds up to 1,000 individual operations (Microsoft Learn). That turns 40,000 individual requests into 40 batch requests, well below the limit of 6,000 per window (Microsoft Learn). This reduces not only the number of calls but also the protocol overhead per record. It is important to test the batch size: batches that are too large increase the execution time per request and can hit the 1,200-second limit (Microsoft Learn).
- Stagger the batch size: Start with a moderate size and feel your way up based on execution time and error rate, rather than using the maximum straight away.
- Separate read and write paths: Read deltas first, then write in bundles, so that not every reconciliation triggers a full import.
- Transfer only changes: A delta sync transfers changed records instead of the entire catalogue and keeps the volume permanently small, the basis of any stable inventory system integration.
- Evaluate errors per operation: Within a batch a single record can fail; the response must be checked per operation and only the faulty part retried.
Managing Concurrency Deliberately
More concurrency sounds like more speed, but with cloud ERPs it quickly turns into the opposite. Dataverse caps the number of concurrent requests at 52 (Microsoft Learn); starting with a hundred parallel connections produces a wave of 429 responses immediately. What works is a fixed, conservative number of parallel workers, say four to eight, combined with a queue that only feeds in new batches once a worker becomes free. That keeps throughput high without breaching the limit. This control belongs in a middleware between ERP and shop, not in each script separately.
If throttling still occurs, concurrency should back off dynamically: when 429 responses pile up, the import reduces the number of active workers until the errors subside, then carefully raises it again. This adaptive behaviour keeps the import in the safe zone without anyone intervening at night. For connections with several sales channels the same principle applies across channels, so that shop and marketplace do not run against the same limit at the same time.
| Criterion | Naive Bulk Import | Throttled Import |
|---|---|---|
| Requests | One request per record | Bundled per batch, up to 1,000 per call |
| Concurrency | Unlimited, often above 52 at once | Fixed worker count with a queue |
| Reaction to 429 | Abort or immediate retry | Wait for Retry-After, then retry |
| Failure case | Half a data state, manual rework | Resume from the last point |
| Result | Inconsistent prices and stock | Complete, consistent transfer |
| Operation | Nightly intervention needed | Runs unattended |
Retry-After Handling and Backoff with Jitter
Not every error is a 429. For transient server and network errors, such as a 5xx status, there is no Retry-After header, and the import has to determine the wait itself. Exponential backoff has proven itself: the pause grows with each attempt, typically following the pattern base times two to the power of attempt, capped by an upper limit (AWS). Without a random element, however, all waiting workers set off again in sync and create a shared surge. That is why you add jitter, a random component that spreads the retries over time (AWS). Google Cloud enables this jitter in its client libraries by default (Google Cloud).
In practice a manageable number of retries is enough: three to five attempts per batch catch most transient disruptions (Google Cloud), while an upper bound of around 10 to 30 seconds per pause prevents a single batch from blocking the whole run (Google Cloud). For the 429 itself, the Retry-After header still takes precedence; backoff with jitter is the answer to everything the server does not acknowledge with a concrete wait. Both mechanisms belong in the same retry logic and can be configured centrally in a tailored interface.
Ensuring Consistency: Idempotency and Resume Points
Retries are only harmless if a record sent twice does not arrive twice. That is why writes must be idempotent: an upsert via a business key, such as the item number, creates or updates the record regardless of how often it is sent. This lets a batch retried after a 429 run again safely. In addition, the import stores a resume point after each transferred batch. If the run aborts, it continues from there on the next start instead of beginning again.
How tight this interplay of batch, retry and resume point has to be shows in the connection via the SAP Business One Service Layer, which manages sessions and transactions on its own. There, too, the rule holds: an import that knows its progress and keeps writes idempotent survives throttling and connection drops without an inconsistent interim state.
A Limit Is a Cadence, Not an Obstacle
SAP and Dynamics Compared
The limits differ by system, the underlying principle stays the same. Microsoft Dataverse states its values openly: 6,000 requests, 1,200 seconds of execution time and 52 concurrent requests per window (Microsoft Learn). Dynamics 365 Finance and Operations adds priority-based throttling, in which low-priority integrations are throttled first and high-priority ones run longest (Microsoft Learn). SAP, by contrast, does not publish one uniform global limit; throttling applies per API and tenant, and when in doubt a support case clarifies the concrete value (SAP Knowledge Base). Despite status 429 (RFC 6585) as the shared signal, it pays to design the connection to SAP and Dynamics around the documented behaviour of the target system in each case.
Implementation Step by Step
- Survey the target system limits: Gather the documented thresholds of Dynamics 365 or SAP and weigh the nightly demand against them.
- Measure the volume: Determine the number of items, prices and stock movements per run and derive the required throughput per window.
- Introduce batching: Bundle writes into batches and set the batch size based on execution time and error rate.
- Add Retry-After and backoff logic: Respect 429 with Retry-After, retry 5xx with exponential backoff and jitter.
- Secure idempotency and resume points: Upserts via business keys and stored progress, so that retries create no duplicates.
- Monitor: Log throttling events, run time and transferred records to keep adjusting batch size and concurrency.
A bulk import does not get faster by ignoring the limit, but by knowing it and setting its cadence accordingly.