API Throughput for Business Central
Moving large volumes of data between systems shouldn’t mean timeouts, duplicates, or late month-ends. In this blog, we share the four habits we use in real projects – pagination, throttling, idempotency, and backoff – to keep integrations fast, predictable, and safe. We’ll show how they fit together in Business Central (BC), where teams go wrong, and what to implement first.
Who this is for
Owners, finance managers, and ops leads in SMEs connecting eCommerce, WMS, and web apps to Dynamics 365 Business Central. If you’re planning an integration or want your existing one to stop stalling at peak times, start here.
The problem in one minute
APIs are shared roads with speed limits. Go too fast and you’ll hit rate limits (429 errors). Go too big and you’ll run out of memory or time.
When networks wobble, retries can create duplicate documents. The fix isn’t one trick—it’s a small set of practices that work together:
-
Pagination – fetch/process in small, ordered batches.
-
Throttling – shape your request rate to stay under limits.
-
Idempotency – make retries safe (no duplicates).
-
Backoff – slow down smartly when the API is busy.
How the four habits fit together (the flow)
- Queue the work. Build a list of items to sync (e.g., orders modified since T-1).
- Paginate. Pull a page (stable order), process, checkpoint, repeat.
- Throttle. Respect BC’s rate caps and your own concurrency.
- Retry with idempotency. If a call fails, retry safely using unique keys.
- Backoff with jitter. If you see “try later”, wait, then try again with an increased wait.
- Checkpoint & monitor. Record last success; track throughput, error % and p95 latency.
If you find this article useful, click and subscribe to our newsletter - Business Central Uplugged - helping you use what you've already paid for!
1) Pagination — keep the chunks small and stable
What’s changed / what’s current in BC
-
Server-driven paging: When a result exceeds the server page size, BC returns an
@odata.nextLink
. Follow that link as-is until it disappears; treat the token as opaque. -
Max page size: BC Online uses a maximum of 20,000 entities per page (can’t be changed online; on-prem can be configured). You can request smaller pages with
Prefer: odata.maxpagesize=N
(N ≤ 20,000) to reduce memory/latency and avoid timeouts.
What good looks like
-
Choose a stable sort (for example,
lastModifiedDateTime
thenid
). -
Follow
@odata.nextLink
when present; otherwise, if you deliberately want smaller chunks, addPrefer: odata.maxpagesize=…
. -
Checkpoint after each page so you can resume mid-run without re-processing.
Business Central angle (extras that help):
If you’re doing heavy reads, add Data-Access-Intent: ReadOnly
on GETs to hit read replicas and reduce load on primaries.
2) Throttling — stay fast without tripping limits
BC Online limits to design around (current at April–September 2025):
-
Per environment (OData): speed cap 600/min in Production, 300/min in Sandbox; max concurrent 5; max connections 100; max queue 95; max batch 100 ops; operation timeout 8 minutes; max page 20,000.
-
Per user (newer model): 6,000 OData requests per 5-minute sliding window plus the same concurrency/queue caps applied per user (spreading load across service users scales throughput, subject to platform capacity).
What good looks like
-
Detect 429s and pace requests (requests/sec) so you stay under the caps.
-
Cap concurrency per endpoint; put workers behind a token/leaky bucket or simple queue so bursts don’t overwhelm the API.
-
If you need to push higher throughput, distribute calls across multiple application users while staying within licence and security policy.
3) Idempotency — retries without duplicates
Why it matters: Networks fail. If a “Create” times out, you’ll retry. Without idempotency, you risk double-creating documents.
BC-specific truths
-
Don’t rely on External Document No. It’s useful for search, but not unique by default, so it won’t prevent duplicates. Use a dedicated external ID field (with a unique index) or a small mapping table (External ID ↔
systemId
), and enforce uniqueness in AL or middleware. -
Prefer upserts (create-or-update) by first searching on your external key; treat replays as update/no-op rather than insert.
-
For updates, use optimistic concurrency: send the
@odata.etag
back inIf-Match
so you don’t overwrite someone else’s change. PATCH requiresIf-Match
in BC.
4) Backoff — be polite when the API is busy
What’s current in BC error handling
-
502 / 503: BC includes a
Retry-After
header—honour it. -
429: treat as “slow down”.
Retry-After
may not be present, so fall back to capped exponential backoff with jitter. -
Timeouts: OData requests time out after ~8 minutes; split work accordingly.
What good looks like
-
Classify errors: retryable (timeouts, 429, 502, 503) vs non-retryable (validation/business rule failures).
-
Implement exponential backoff with jitter, plus sensible caps (max attempts, max backoff), and a dead-letter queue for items that need human fixes.
What to implement first (if you only have a day)
- Add pagination with explicit ordering and checkpoints; follow
@odata.nextLink
and/or sendPrefer: odata.maxpagesize=
for smaller chunks. - Wrap calls with retry + backoff (use
Retry-After
when present on 502/503). - Introduce a dedicated external ID and enforce uniqueness; don’t rely on External Document No.
- Throttle to a steady rate and keep within env/user caps; then tune page size and concurrency by measurement.
Monitoring that actually helps
-
Throughput: items/minute and pages/minute.
-
Quality: error rate, retries per success.
-
Latency: p95/p99 per endpoint.
-
Limits: 429 counts, average
Retry-After
(where present). -
Recovery: dead-letter volume and age.
Common mistakes (and easy fixes)
-
“We’ll just retry everything.”
Don’t. Classify errors; never retry validation failures. -
“Bigger pages are faster.”
Often slower overall. Start modest, measure, then tune. -
“Duplicates are a data problem.”
They’re a design problem. Add idempotency keys. -
“429s are harmless.”
They’re a signal to back off and/or lower concurrency.
Throughput boosters you can use safely
-
Webhooks for standard/custom APIs to reduce polling. Subscriptions require a handshake (return
validationToken
), expire after ~3 days, and must be renewed; BC Online allows up to 200 subscriptions. -
Batching (
$batch
) to cut round trips—max 100 operations per batch. Use with care alongside idempotency. Microsoft Learn
Quick reference: the four habits at a glance
Habit | Primary goal | Implement with… | Anti-pattern to avoid |
---|---|---|---|
Pagination | Reliability & memory safety | Server-driven paging (@odata.nextLink ), Prefer: odata.maxpagesize , checkpoints |
Huge, ad-hoc batches |
Throttling | Stay under rate limits | Rate caps, per-user windows, queues | Spiky bursts that trigger 429s |
Idempotency | Safe retries, no duplicates | Dedicated external ID + upsert, ETag/If-Match |
Blind “create” on every retry |
Backoff | Graceful recovery | Exponential backoff + jitter, honour Retry-After |
Immediate, repeated hammering |
Implementation checklist
-
Follow
@odata.nextLink
and/or setPrefer: odata.maxpagesize
; checkpoint after each page. -
Add a throttle (req/s) and cap concurrency per endpoint; design to env/user caps.
- Persist a dedicated external ID (or mapping) and enforce uniqueness in AL/middleware.
- Implement retry policy: retryable vs non-retryable; exponential backoff with jitter; use
Retry-After
where present; max attempts. - Instrument metrics and alerts for throughput, errors, 429s, timeouts.
- Document the runbook: how to re-run from checkpoint; how to drain the dead-letter queue.
FAQs
What page size should we start with?
@odata.nextLink
when it appears, and usePrefer: odata.maxpagesize=...
for smaller chunks if pages are slow.What’s the difference between throttling and backoff?
Backoff is a temporary slow-down when the API signals “too busy” — you pause
longer between retries, then resume normal speed.
How do idempotency keys prevent duplicates?
target treats it as the same logical operation — update or no-op, not a new record.
Don’t rely on External Document No.; it isn’t unique by default.
Which errors should we retry?
honour any
Retry-After
header. Don’t retry validation or business-rule errors —surface them for a human fix.
Does Business Central have hard numbers for API limits?
Yes. Design to the documented caps and monitor them:
- Production speed cap: ~600 requests/min per environment
- Sandbox speed cap: ~300 requests/min per environment
- Max concurrent OData operations: 5
- Queue size: ~95; Max batch operations: 100
- OData operation timeout: ~8 minutes
- Per-user window: ~6,000 requests per 5-minute sliding window
How we can help
We’ve implemented these patterns across multiple Business Central integrations for SMEs. If you want a second pair of eyes or a quicker route to a stable month-end, book a 30-minute Integration Review. We’ll assess your current flow and give you a practical action plan.
Enter your details below or call us on +44 (0) 1782 976577